home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / iceweasel / modules / HUDService.jsm < prev    next >
Encoding:
Text File  |  2013-01-09  |  213.4 KB  |  7,150 lines

  1. /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
  2. /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
  3. /* ***** BEGIN LICENSE BLOCK *****
  4.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5.  *
  6.  * The contents of this file are subject to the Mozilla Public License Version
  7.  * 1.1 (the "License"); you may not use this file except in compliance with
  8.  * the License. You may obtain a copy of the License at
  9.  * http://www.mozilla.org/MPL/
  10.  *
  11.  * Software distributed under the License is distributed on an "AS IS" basis,
  12.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13.  * for the specific language governing rights and limitations under the
  14.  * License.
  15.  *
  16.  * The Original Code is DevTools (HeadsUpDisplay) Console Code
  17.  *
  18.  * The Initial Developer of the Original Code is
  19.  *   Mozilla Foundation
  20.  * Portions created by the Initial Developer are Copyright (C) 2010
  21.  * the Initial Developer. All Rights Reserved.
  22.  *
  23.  * Contributor(s):
  24.  *   David Dahl <ddahl@mozilla.com> (original author)
  25.  *   Rob Campbell <rcampbell@mozilla.com>
  26.  *   Johnathan Nightingale <jnightingale@mozilla.com>
  27.  *   Patrick Walton <pcwalton@mozilla.com>
  28.  *   Julian Viereck <jviereck@mozilla.com>
  29.  *   Mihai ╚ÿucan <mihai.sucan@gmail.com>
  30.  *   Michael Ratcliffe <mratcliffe@mozilla.com>
  31.  *   Joe Walker <jwalker@mozilla.com>
  32.  *
  33.  * Alternatively, the contents of this file may be used under the terms of
  34.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  35.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  36.  * in which case the provisions of the GPL or the LGPL are applicable instead
  37.  * of those above. If you wish to allow use of your version of this file only
  38.  * under the terms of either the GPL or the LGPL, and not to allow others to
  39.  * use your version of this file under the terms of the MPL, indicate your
  40.  * decision by deleting the provisions above and replace them with the notice
  41.  * and other provisions required by the GPL or the LGPL. If you do not delete
  42.  * the provisions above, a recipient may use your version of this file under
  43.  * the terms of any one of the MPL, the GPL or the LGPL.
  44.  *
  45.  * ***** END LICENSE BLOCK ***** */
  46.  
  47. const Cc = Components.classes;
  48. const Ci = Components.interfaces;
  49. const Cu = Components.utils;
  50.  
  51. const CONSOLEAPI_CLASS_ID = "{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}";
  52.  
  53. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  54. Cu.import("resource://gre/modules/Services.jsm");
  55. Cu.import("resource:///modules/NetworkHelper.jsm");
  56. Cu.import("resource:///modules/PropertyPanel.jsm");
  57.  
  58. var EXPORTED_SYMBOLS = ["HUDService", "ConsoleUtils"];
  59.  
  60. XPCOMUtils.defineLazyServiceGetter(this, "scriptError",
  61.                                    "@mozilla.org/scripterror;1",
  62.                                    "nsIScriptError");
  63.  
  64. XPCOMUtils.defineLazyServiceGetter(this, "activityDistributor",
  65.                                    "@mozilla.org/network/http-activity-distributor;1",
  66.                                    "nsIHttpActivityDistributor");
  67.  
  68. XPCOMUtils.defineLazyServiceGetter(this, "mimeService",
  69.                                    "@mozilla.org/mime;1",
  70.                                    "nsIMIMEService");
  71.  
  72. XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
  73.                                    "@mozilla.org/widget/clipboardhelper;1",
  74.                                    "nsIClipboardHelper");
  75.  
  76. XPCOMUtils.defineLazyGetter(this, "gcli", function () {
  77.   var obj = {};
  78.   Cu.import("resource:///modules/gcli.jsm", obj);
  79.   return obj.gcli;
  80. });
  81.  
  82. XPCOMUtils.defineLazyGetter(this, "CssRuleView", function() {
  83.   let tmp = {};
  84.   Cu.import("resource:///modules/devtools/CssRuleView.jsm", tmp);
  85.   return tmp.CssRuleView;
  86. });
  87.  
  88. XPCOMUtils.defineLazyGetter(this, "NetUtil", function () {
  89.   var obj = {};
  90.   Cu.import("resource://gre/modules/NetUtil.jsm", obj);
  91.   return obj.NetUtil;
  92. });
  93.  
  94. XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () {
  95.   var obj = {};
  96.   try {
  97.     Cu.import("resource:///modules/PropertyPanel.jsm", obj);
  98.   } catch (err) {
  99.     Cu.reportError(err);
  100.   }
  101.   return obj.PropertyPanel;
  102. });
  103.  
  104. XPCOMUtils.defineLazyGetter(this, "AutocompletePopup", function () {
  105.   var obj = {};
  106.   try {
  107.     Cu.import("resource:///modules/AutocompletePopup.jsm", obj);
  108.   }
  109.   catch (err) {
  110.     Cu.reportError(err);
  111.   }
  112.   return obj.AutocompletePopup;
  113. });
  114.  
  115. XPCOMUtils.defineLazyGetter(this, "namesAndValuesOf", function () {
  116.   var obj = {};
  117.   Cu.import("resource:///modules/PropertyPanel.jsm", obj);
  118.   return obj.namesAndValuesOf;
  119. });
  120.  
  121. function LogFactory(aMessagePrefix)
  122. {
  123.   function log(aMessage) {
  124.     var _msg = aMessagePrefix + " " + aMessage + "\n";
  125.     dump(_msg);
  126.   }
  127.   return log;
  128. }
  129.  
  130. /**
  131.  * Load the various Command JSMs.
  132.  * Should be called when the console first opens.
  133.  *
  134.  * @return an object containing the EXPORTED_SYMBOLS from all the command
  135.  * modules. In general there is no reason when JSMs need to export symbols
  136.  * except when they need the host environment to inform them of things like the
  137.  * current window/document/etc.
  138.  */
  139. function loadCommands() {
  140.   let commandExports = {};
  141.  
  142.   Cu.import("resource:///modules/GcliCommands.jsm", commandExports);
  143.  
  144.   return commandExports;
  145. }
  146.  
  147. let log = LogFactory("*** HUDService:");
  148.  
  149. const HUD_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
  150.  
  151. XPCOMUtils.defineLazyGetter(this, "stringBundle", function () {
  152.   return Services.strings.createBundle(HUD_STRINGS_URI);
  153. });
  154.  
  155. // The amount of time in milliseconds that must pass between messages to
  156. // trigger the display of a new group.
  157. const NEW_GROUP_DELAY = 5000;
  158.  
  159. // The amount of time in milliseconds that we wait before performing a live
  160. // search.
  161. const SEARCH_DELAY = 200;
  162.  
  163. // The number of lines that are displayed in the console output by default, for
  164. // each category. The user can change this number by adjusting the hidden
  165. // "devtools.hud.loglimit.{network,cssparser,exception,console}" preferences.
  166. const DEFAULT_LOG_LIMIT = 200;
  167.  
  168. // The various categories of messages. We start numbering at zero so we can
  169. // use these as indexes into the MESSAGE_PREFERENCE_KEYS matrix below.
  170. const CATEGORY_NETWORK = 0;
  171. const CATEGORY_CSS = 1;
  172. const CATEGORY_JS = 2;
  173. const CATEGORY_WEBDEV = 3;
  174. const CATEGORY_INPUT = 4;   // always on
  175. const CATEGORY_OUTPUT = 5;  // always on
  176.  
  177. // The possible message severities. As before, we start at zero so we can use
  178. // these as indexes into MESSAGE_PREFERENCE_KEYS.
  179. const SEVERITY_ERROR = 0;
  180. const SEVERITY_WARNING = 1;
  181. const SEVERITY_INFO = 2;
  182. const SEVERITY_LOG = 3;
  183.  
  184. // A mapping from the console API log event levels to the Web Console
  185. // severities.
  186. const LEVELS = {
  187.   error: SEVERITY_ERROR,
  188.   warn: SEVERITY_WARNING,
  189.   info: SEVERITY_INFO,
  190.   log: SEVERITY_LOG,
  191.   trace: SEVERITY_LOG,
  192.   dir: SEVERITY_LOG,
  193.   group: SEVERITY_LOG,
  194.   groupCollapsed: SEVERITY_LOG,
  195.   groupEnd: SEVERITY_LOG,
  196.   time: SEVERITY_LOG,
  197.   timeEnd: SEVERITY_LOG
  198. };
  199.  
  200. // The lowest HTTP response code (inclusive) that is considered an error.
  201. const MIN_HTTP_ERROR_CODE = 400;
  202. // The highest HTTP response code (exclusive) that is considered an error.
  203. const MAX_HTTP_ERROR_CODE = 600;
  204.  
  205. // HTTP status codes.
  206. const HTTP_MOVED_PERMANENTLY = 301;
  207. const HTTP_FOUND = 302;
  208. const HTTP_SEE_OTHER = 303;
  209. const HTTP_TEMPORARY_REDIRECT = 307;
  210.  
  211. // The HTML namespace.
  212. const HTML_NS = "http://www.w3.org/1999/xhtml";
  213.  
  214. // The XUL namespace.
  215. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  216.  
  217. // The fragment of a CSS class name that identifies each category.
  218. const CATEGORY_CLASS_FRAGMENTS = [
  219.   "network",
  220.   "cssparser",
  221.   "exception",
  222.   "console",
  223.   "input",
  224.   "output",
  225. ];
  226.  
  227. // The fragment of a CSS class name that identifies each severity.
  228. const SEVERITY_CLASS_FRAGMENTS = [
  229.   "error",
  230.   "warn",
  231.   "info",
  232.   "log",
  233. ];
  234.  
  235. // The preference keys to use for each category/severity combination, indexed
  236. // first by category (rows) and then by severity (columns).
  237. //
  238. // Most of these rather idiosyncratic names are historical and predate the
  239. // division of message type into "category" and "severity".
  240. const MESSAGE_PREFERENCE_KEYS = [
  241. //  Error         Warning   Info    Log
  242.   [ "network",    null,         null,   "networkinfo", ],  // Network
  243.   [ "csserror",   "cssparser",  null,   null,          ],  // CSS
  244.   [ "exception",  "jswarn",     null,   null,          ],  // JS
  245.   [ "error",      "warn",       "info", "log",         ],  // Web Developer
  246.   [ null,         null,         null,   null,          ],  // Input
  247.   [ null,         null,         null,   null,          ],  // Output
  248. ];
  249.  
  250. // Possible directions that can be passed to HUDService.animate().
  251. const ANIMATE_OUT = 0;
  252. const ANIMATE_IN = 1;
  253.  
  254. // Constants used for defining the direction of JSTerm input history navigation.
  255. const HISTORY_BACK = -1;
  256. const HISTORY_FORWARD = 1;
  257.  
  258. // The maximum number of bytes a Network ResponseListener can hold.
  259. const RESPONSE_BODY_LIMIT = 1024*1024; // 1 MB
  260.  
  261. // The maximum uint32 value.
  262. const PR_UINT32_MAX = 4294967295;
  263.  
  264. // Minimum console height, in pixels.
  265. const MINIMUM_CONSOLE_HEIGHT = 150;
  266.  
  267. // Minimum page height, in pixels. This prevents the Web Console from
  268. // remembering a height that covers the whole page.
  269. const MINIMUM_PAGE_HEIGHT = 50;
  270.  
  271. // The default console height, as a ratio from the content window inner height.
  272. const DEFAULT_CONSOLE_HEIGHT = 0.33;
  273.  
  274. // Constant used when checking the typeof objects.
  275. const TYPEOF_FUNCTION = "function";
  276.  
  277. const ERRORS = { LOG_MESSAGE_MISSING_ARGS:
  278.                  "Missing arguments: aMessage, aConsoleNode and aMessageNode are required.",
  279.                  CANNOT_GET_HUD: "Cannot getHeads Up Display with provided ID",
  280.                  MISSING_ARGS: "Missing arguments",
  281.                  LOG_OUTPUT_FAILED: "Log Failure: Could not append messageNode to outputNode",
  282. };
  283.  
  284. // The indent of a console group in pixels.
  285. const GROUP_INDENT = 12;
  286.  
  287. /**
  288.  * Implements the nsIStreamListener and nsIRequestObserver interface. Used
  289.  * within the HS_httpObserverFactory function to get the response body of
  290.  * requests.
  291.  *
  292.  * The code is mostly based on code listings from:
  293.  *
  294.  *   http://www.softwareishard.com/blog/firebug/
  295.  *      nsitraceablechannel-intercept-http-traffic/
  296.  *
  297.  * @param object aHttpActivity
  298.  *        HttpActivity object associated with this request (see
  299.  *        HS_httpObserverFactory). As the response is done, the response header,
  300.  *        body and status is stored on aHttpActivity.
  301.  */
  302. function ResponseListener(aHttpActivity) {
  303.   this.receivedData = "";
  304.   this.httpActivity = aHttpActivity;
  305. }
  306.  
  307. ResponseListener.prototype =
  308. {
  309.   /**
  310.    * The response will be written into the outputStream of this nsIPipe.
  311.    * Both ends of the pipe must be blocking.
  312.    */
  313.   sink: null,
  314.  
  315.   /**
  316.    * The HttpActivity object associated with this response.
  317.    */
  318.   httpActivity: null,
  319.  
  320.   /**
  321.    * Stores the received data as a string.
  322.    */
  323.   receivedData: null,
  324.  
  325.   /**
  326.    * The nsIRequest we are started for.
  327.    */
  328.   request: null,
  329.  
  330.   /**
  331.    * Sets the httpActivity object's response header if it isn't set already.
  332.    *
  333.    * @param nsIRequest aRequest
  334.    */
  335.   setResponseHeader: function RL_setResponseHeader(aRequest)
  336.   {
  337.     let httpActivity = this.httpActivity;
  338.     // Check if the header isn't set yet.
  339.     if (!httpActivity.response.header) {
  340.       if (aRequest instanceof Ci.nsIHttpChannel) {
  341.       httpActivity.response.header = {};
  342.         try {
  343.         aRequest.visitResponseHeaders({
  344.           visitHeader: function(aName, aValue) {
  345.             httpActivity.response.header[aName] = aValue;
  346.           }
  347.         });
  348.       }
  349.         // Accessing the response header can throw an NS_ERROR_NOT_AVAILABLE
  350.         // exception. Catch it and stop it to make it not show up in the.
  351.         // This can happen if the response is not finished yet and the user
  352.         // reloades the page.
  353.         catch (ex) {
  354.           delete httpActivity.response.header;
  355.         }
  356.       }
  357.     }
  358.   },
  359.  
  360.   /**
  361.    * Set the async listener for the given nsIAsyncInputStream. This allows us to
  362.    * wait asynchronously for any data coming from the stream.
  363.    *
  364.    * @param nsIAsyncInputStream aStream
  365.    *        The input stream from where we are waiting for data to come in.
  366.    *
  367.    * @param nsIInputStreamCallback aListener
  368.    *        The input stream callback you want. This is an object that must have
  369.    *        the onInputStreamReady() method. If the argument is null, then the
  370.    *        current callback is removed.
  371.    *
  372.    * @returns void
  373.    */
  374.   setAsyncListener: function RL_setAsyncListener(aStream, aListener)
  375.   {
  376.     // Asynchronously wait for the stream to be readable or closed.
  377.     aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
  378.   },
  379.  
  380.   /**
  381.    * Stores the received data, if request/response body logging is enabled. It
  382.    * also does limit the number of stored bytes, based on the
  383.    * RESPONSE_BODY_LIMIT constant.
  384.    *
  385.    * Learn more about nsIStreamListener at:
  386.    * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
  387.    *
  388.    * @param nsIRequest aRequest
  389.    * @param nsISupports aContext
  390.    * @param nsIInputStream aInputStream
  391.    * @param unsigned long aOffset
  392.    * @param unsigned long aCount
  393.    */
  394.   onDataAvailable: function RL_onDataAvailable(aRequest, aContext, aInputStream,
  395.                                                aOffset, aCount)
  396.   {
  397.     this.setResponseHeader(aRequest);
  398.  
  399.     let data = NetUtil.readInputStreamToString(aInputStream, aCount);
  400.  
  401.     if (!this.httpActivity.response.bodyDiscarded &&
  402.         this.receivedData.length < RESPONSE_BODY_LIMIT) {
  403.       this.receivedData += NetworkHelper.
  404.                            convertToUnicode(data, aRequest.contentCharset);
  405.     }
  406.   },
  407.  
  408.   /**
  409.    * See documentation at
  410.    * https://developer.mozilla.org/En/NsIRequestObserver
  411.    *
  412.    * @param nsIRequest aRequest
  413.    * @param nsISupports aContext
  414.    */
  415.   onStartRequest: function RL_onStartRequest(aRequest, aContext)
  416.   {
  417.     this.request = aRequest;
  418.  
  419.     // Always discard the response body if logging is not enabled in the Web
  420.     // Console.
  421.     this.httpActivity.response.bodyDiscarded =
  422.       !HUDService.saveRequestAndResponseBodies;
  423.  
  424.     // Check response status and discard the body for redirects.
  425.     if (!this.httpActivity.response.bodyDiscarded &&
  426.         this.httpActivity.channel instanceof Ci.nsIHttpChannel) {
  427.       switch (this.httpActivity.channel.responseStatus) {
  428.         case HTTP_MOVED_PERMANENTLY:
  429.         case HTTP_FOUND:
  430.         case HTTP_SEE_OTHER:
  431.         case HTTP_TEMPORARY_REDIRECT:
  432.           this.httpActivity.response.bodyDiscarded = true;
  433.           break;
  434.       }
  435.     }
  436.  
  437.     // Asynchronously wait for the data coming from the request.
  438.     this.setAsyncListener(this.sink.inputStream, this);
  439.   },
  440.  
  441.   /**
  442.    * Handle the onStopRequest by storing the response header is stored on the
  443.    * httpActivity object. The sink output stream is also closed.
  444.    *
  445.    * For more documentation about nsIRequestObserver go to:
  446.    * https://developer.mozilla.org/En/NsIRequestObserver
  447.    *
  448.    * @param nsIRequest aRequest
  449.    *        The request we are observing.
  450.    * @param nsISupports aContext
  451.    * @param nsresult aStatusCode
  452.    */
  453.   onStopRequest: function RL_onStopRequest(aRequest, aContext, aStatusCode)
  454.   {
  455.     // Retrieve the response headers, as they are, from the server.
  456.     let response = null;
  457.     for each (let item in HUDService.openResponseHeaders) {
  458.       if (item.channel === this.httpActivity.channel) {
  459.         response = item;
  460.         break;
  461.       }
  462.     }
  463.  
  464.     if (response) {
  465.       this.httpActivity.response.header = response.headers;
  466.       delete HUDService.openResponseHeaders[response.id];
  467.     }
  468.     else {
  469.       this.setResponseHeader(aRequest);
  470.     }
  471.  
  472.     this.sink.outputStream.close();
  473.   },
  474.  
  475.   /**
  476.    * Clean up the response listener once the response input stream is closed.
  477.    * This is called from onStopRequest() or from onInputStreamReady() when the
  478.    * stream is closed.
  479.    *
  480.    * @returns void
  481.    */
  482.   onStreamClose: function RL_onStreamClose()
  483.   {
  484.     if (!this.httpActivity) {
  485.       return;
  486.     }
  487.  
  488.     // Remove our listener from the request input stream.
  489.     this.setAsyncListener(this.sink.inputStream, null);
  490.  
  491.     if (!this.httpActivity.response.bodyDiscarded &&
  492.         HUDService.saveRequestAndResponseBodies) {
  493.       this.httpActivity.response.body = this.receivedData;
  494.     }
  495.  
  496.     if (HUDService.lastFinishedRequestCallback) {
  497.       HUDService.lastFinishedRequestCallback(this.httpActivity);
  498.     }
  499.  
  500.     // Call update on all panels.
  501.     this.httpActivity.panels.forEach(function(weakRef) {
  502.       let panel = weakRef.get();
  503.       if (panel) {
  504.         panel.update();
  505.       }
  506.     });
  507.     this.httpActivity.response.isDone = true;
  508.     this.httpActivity = null;
  509.     this.receivedData = "";
  510.     this.request = null;
  511.     this.sink = null;
  512.     this.inputStream = null;
  513.   },
  514.  
  515.   /**
  516.    * The nsIInputStreamCallback for when the request input stream is ready -
  517.    * either it has more data or it is closed.
  518.    *
  519.    * @param nsIAsyncInputStream aStream
  520.    *        The sink input stream from which data is coming.
  521.    *
  522.    * @returns void
  523.    */
  524.   onInputStreamReady: function RL_onInputStreamReady(aStream)
  525.   {
  526.     if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
  527.       return;
  528.     }
  529.  
  530.     let available = -1;
  531.     try {
  532.       // This may throw if the stream is closed normally or due to an error.
  533.       available = aStream.available();
  534.     }
  535.     catch (ex) { }
  536.  
  537.     if (available != -1) {
  538.       if (available != 0) {
  539.         // Note that passing 0 as the offset here is wrong, but the
  540.         // onDataAvailable() method does not use the offset, so it does not
  541.         // matter.
  542.         this.onDataAvailable(this.request, null, aStream, 0, available);
  543.       }
  544.       this.setAsyncListener(aStream, this);
  545.     }
  546.     else {
  547.       this.onStreamClose();
  548.     }
  549.   },
  550.  
  551.   QueryInterface: XPCOMUtils.generateQI([
  552.     Ci.nsIStreamListener,
  553.     Ci.nsIInputStreamCallback,
  554.     Ci.nsIRequestObserver,
  555.     Ci.nsISupports,
  556.   ])
  557. }
  558.  
  559. ///////////////////////////////////////////////////////////////////////////
  560. //// Helper for creating the network panel.
  561.  
  562. /**
  563.  * Creates a DOMNode and sets all the attributes of aAttributes on the created
  564.  * element.
  565.  *
  566.  * @param nsIDOMDocument aDocument
  567.  *        Document to create the new DOMNode.
  568.  * @param string aTag
  569.  *        Name of the tag for the DOMNode.
  570.  * @param object aAttributes
  571.  *        Attributes set on the created DOMNode.
  572.  *
  573.  * @returns nsIDOMNode
  574.  */
  575. function createElement(aDocument, aTag, aAttributes)
  576. {
  577.   let node = aDocument.createElement(aTag);
  578.   if (aAttributes) {
  579.     for (let attr in aAttributes) {
  580.       node.setAttribute(attr, aAttributes[attr]);
  581.     }
  582.   }
  583.   return node;
  584. }
  585.  
  586. /**
  587.  * Creates a new DOMNode and appends it to aParent.
  588.  *
  589.  * @param nsIDOMNode aParent
  590.  *        A parent node to append the created element.
  591.  * @param string aTag
  592.  *        Name of the tag for the DOMNode.
  593.  * @param object aAttributes
  594.  *        Attributes set on the created DOMNode.
  595.  *
  596.  * @returns nsIDOMNode
  597.  */
  598. function createAndAppendElement(aParent, aTag, aAttributes)
  599. {
  600.   let node = createElement(aParent.ownerDocument, aTag, aAttributes);
  601.   aParent.appendChild(node);
  602.   return node;
  603. }
  604.  
  605. /**
  606.  * Convenience function to unwrap a wrapped object.
  607.  *
  608.  * @param aObject the object to unwrap
  609.  */
  610.  
  611. function unwrap(aObject)
  612. {
  613.   try {
  614.     return XPCNativeWrapper.unwrap(aObject);
  615.   } catch(e) {
  616.     return aObject;
  617.   }
  618. }
  619.  
  620. ///////////////////////////////////////////////////////////////////////////
  621. //// NetworkPanel
  622.  
  623. /**
  624.  * Creates a new NetworkPanel.
  625.  *
  626.  * @param nsIDOMNode aParent
  627.  *        Parent node to append the created panel to.
  628.  * @param object aHttpActivity
  629.  *        HttpActivity to display in the panel.
  630.  */
  631. function NetworkPanel(aParent, aHttpActivity)
  632. {
  633.   let doc = aParent.ownerDocument;
  634.   this.httpActivity = aHttpActivity;
  635.  
  636.   // Create the underlaying panel
  637.   this.panel = createElement(doc, "panel", {
  638.     label: HUDService.getStr("NetworkPanel.label"),
  639.     titlebar: "normal",
  640.     noautofocus: "true",
  641.     noautohide: "true",
  642.     close: "true"
  643.   });
  644.  
  645.   // Create the iframe that displays the NetworkPanel XHTML.
  646.   this.iframe = createAndAppendElement(this.panel, "iframe", {
  647.     src: "chrome://browser/content/NetworkPanel.xhtml",
  648.     type: "content",
  649.     flex: "1"
  650.   });
  651.  
  652.   let self = this;
  653.  
  654.   // Destroy the panel when it's closed.
  655.   this.panel.addEventListener("popuphidden", function onPopupHide() {
  656.     self.panel.removeEventListener("popuphidden", onPopupHide, false);
  657.     self.panel.parentNode.removeChild(self.panel);
  658.     self.panel = null;
  659.     self.iframe = null;
  660.     self.document = null;
  661.     self.httpActivity = null;
  662.  
  663.     if (self.linkNode) {
  664.       self.linkNode._panelOpen = false;
  665.       self.linkNode = null;
  666.     }
  667.   }, false);
  668.  
  669.   // Set the document object and update the content once the panel is loaded.
  670.   this.panel.addEventListener("load", function onLoad() {
  671.     self.panel.removeEventListener("load", onLoad, true);
  672.     self.document = self.iframe.contentWindow.document;
  673.     self.update();
  674.   }, true);
  675.  
  676.   // Create the footer.
  677.   let footer = createElement(doc, "hbox", { align: "end" });
  678.   createAndAppendElement(footer, "spacer", { flex: 1 });
  679.  
  680.   createAndAppendElement(footer, "resizer", { dir: "bottomend" });
  681.   this.panel.appendChild(footer);
  682.  
  683.   aParent.appendChild(this.panel);
  684. }
  685.  
  686. NetworkPanel.prototype =
  687. {
  688.   /**
  689.    * Callback is called once the NetworkPanel is processed completly. Used by
  690.    * unit tests.
  691.    */
  692.   isDoneCallback: null,
  693.  
  694.   /**
  695.    * The current state of the output.
  696.    */
  697.   _state: 0,
  698.  
  699.   /**
  700.    * State variables.
  701.    */
  702.   _INIT: 0,
  703.   _DISPLAYED_REQUEST_HEADER: 1,
  704.   _DISPLAYED_REQUEST_BODY: 2,
  705.   _DISPLAYED_RESPONSE_HEADER: 3,
  706.   _TRANSITION_CLOSED: 4,
  707.  
  708.   _fromDataRegExp: /Content-Type\:\s*application\/x-www-form-urlencoded/,
  709.  
  710.   /**
  711.    * Small helper function that is nearly equal to  HUDService.getFormatStr
  712.    * except that it prefixes aName with "NetworkPanel.".
  713.    *
  714.    * @param string aName
  715.    *        The name of an i10n string to format. This string is prefixed with
  716.    *        "NetworkPanel." before calling the HUDService.getFormatStr function.
  717.    * @param array aArray
  718.    *        Values used as placeholder for the i10n string.
  719.    * @returns string
  720.    *          The i10n formated string.
  721.    */
  722.   _format: function NP_format(aName, aArray)
  723.   {
  724.     return HUDService.getFormatStr("NetworkPanel." + aName, aArray);
  725.   },
  726.  
  727.   /**
  728.    * Returns the content type of the response body. This is based on the
  729.    * response.header["Content-Type"] info. If this value is not available, then
  730.    * the content type is tried to be estimated by the url file ending.
  731.    *
  732.    * @returns string or null
  733.    *          Content type or null if no content type could be figured out.
  734.    */
  735.   get _contentType()
  736.   {
  737.     let response = this.httpActivity.response;
  738.     let contentTypeValue = null;
  739.  
  740.     if (response.header && response.header["Content-Type"]) {
  741.       let types = response.header["Content-Type"].split(/,|;/);
  742.       for (let i = 0; i < types.length; i++) {
  743.         let type = NetworkHelper.mimeCategoryMap[types[i]];
  744.         if (type) {
  745.           return types[i];
  746.         }
  747.       }
  748.     }
  749.  
  750.     // Try to get the content type from the request file extension.
  751.     let uri = NetUtil.newURI(this.httpActivity.url);
  752.     let mimeType = null;
  753.     if ((uri instanceof Ci.nsIURL) && uri.fileExtension) {
  754.       try {
  755.         mimeType = mimeService.getTypeFromExtension(uri.fileExtension);
  756.       } catch(e) {
  757.         // Added to prevent failures on OS X 64. No Flash?
  758.         Cu.reportError(e);
  759.         // Return empty string to pass unittests.
  760.         return "";
  761.       }
  762.     }
  763.     return mimeType;
  764.   },
  765.  
  766.   /**
  767.    *
  768.    * @returns boolean
  769.    *          True if the response is an image, false otherwise.
  770.    */
  771.   get _responseIsImage()
  772.   {
  773.     return NetworkHelper.mimeCategoryMap[this._contentType] == "image";
  774.   },
  775.  
  776.   /**
  777.    *
  778.    * @returns boolean
  779.    *          True if the response body contains text, false otherwise.
  780.    */
  781.   get _isResponseBodyTextData()
  782.   {
  783.     let contentType = this._contentType;
  784.  
  785.     if (!contentType)
  786.       return false;
  787.  
  788.     if (contentType.indexOf("text/") == 0) {
  789.       return true;
  790.     }
  791.  
  792.     switch (NetworkHelper.mimeCategoryMap[contentType]) {
  793.       case "txt":
  794.       case "js":
  795.       case "json":
  796.       case "css":
  797.       case "html":
  798.       case "svg":
  799.       case "xml":
  800.         return true;
  801.  
  802.       default:
  803.         return false;
  804.     }
  805.   },
  806.  
  807.   /**
  808.    *
  809.    * @returns boolean
  810.    *          Returns true if the server responded that the request is already
  811.    *          in the browser's cache, false otherwise.
  812.    */
  813.   get _isResponseCached()
  814.   {
  815.     return this.httpActivity.response.status.indexOf("304") != -1;
  816.   },
  817.  
  818.   /**
  819.    *
  820.    * @returns boolean
  821.    *          Returns true if the posted body contains form data.
  822.    */
  823.   get _isRequestBodyFormData()
  824.   {
  825.     let requestBody = this.httpActivity.request.body;
  826.     return this._fromDataRegExp.test(requestBody);
  827.   },
  828.  
  829.   /**
  830.    * Appends the node with id=aId by the text aValue.
  831.    *
  832.    * @param string aId
  833.    * @param string aValue
  834.    * @returns void
  835.    */
  836.   _appendTextNode: function NP_appendTextNode(aId, aValue)
  837.   {
  838.     let textNode = this.document.createTextNode(aValue);
  839.     this.document.getElementById(aId).appendChild(textNode);
  840.   },
  841.  
  842.   /**
  843.    * Generates some HTML to display the key-value pair of the aList data. The
  844.    * generated HTML is added to node with id=aParentId.
  845.    *
  846.    * @param string aParentId
  847.    *        Id of the parent node to append the list to.
  848.    * @oaram object aList
  849.    *        Object that holds the key-value information to display in aParentId.
  850.    * @param boolean aIgnoreCookie
  851.    *        If true, the key-value named "Cookie" is not added to the list.
  852.    * @returns void
  853.    */
  854.   _appendList: function NP_appendList(aParentId, aList, aIgnoreCookie)
  855.   {
  856.     let parent = this.document.getElementById(aParentId);
  857.     let doc = this.document;
  858.  
  859.     let sortedList = {};
  860.     Object.keys(aList).sort().forEach(function(aKey) {
  861.       sortedList[aKey] = aList[aKey];
  862.     });
  863.  
  864.     for (let key in sortedList) {
  865.       if (aIgnoreCookie && key == "Cookie") {
  866.         continue;
  867.       }
  868.  
  869.       /**
  870.        * The following code creates the HTML:
  871.        * <tr>
  872.        * <th scope="row" class="property-name">${line}:</th>
  873.        * <td class="property-value">${aList[line]}</td>
  874.        * </tr>
  875.        * and adds it to parent.
  876.        */
  877.       let row = doc.createElement("tr");
  878.       let textNode = doc.createTextNode(key + ":");
  879.       let th = doc.createElement("th");
  880.       th.setAttribute("scope", "row");
  881.       th.setAttribute("class", "property-name");
  882.       th.appendChild(textNode);
  883.       row.appendChild(th);
  884.  
  885.       textNode = doc.createTextNode(sortedList[key]);
  886.       let td = doc.createElement("td");
  887.       td.setAttribute("class", "property-value");
  888.       td.appendChild(textNode);
  889.       row.appendChild(td);
  890.  
  891.       parent.appendChild(row);
  892.     }
  893.   },
  894.  
  895.   /**
  896.    * Displays the node with id=aId.
  897.    *
  898.    * @param string aId
  899.    * @returns void
  900.    */
  901.   _displayNode: function NP_displayNode(aId)
  902.   {
  903.     this.document.getElementById(aId).style.display = "block";
  904.   },
  905.  
  906.   /**
  907.    * Sets the request URL, request method, the timing information when the
  908.    * request started and the request header content on the NetworkPanel.
  909.    * If the request header contains cookie data, a list of sent cookies is
  910.    * generated and a special sent cookie section is displayed + the cookie list
  911.    * added to it.
  912.    *
  913.    * @returns void
  914.    */
  915.   _displayRequestHeader: function NP_displayRequestHeader()
  916.   {
  917.     let timing = this.httpActivity.timing;
  918.     let request = this.httpActivity.request;
  919.  
  920.     this._appendTextNode("headUrl", this.httpActivity.url);
  921.     this._appendTextNode("headMethod", this.httpActivity.method);
  922.  
  923.     this._appendTextNode("requestHeadersInfo",
  924.       ConsoleUtils.timestampString(timing.REQUEST_HEADER/1000));
  925.  
  926.     this._appendList("requestHeadersContent", request.header, true);
  927.  
  928.     if ("Cookie" in request.header) {
  929.       this._displayNode("requestCookie");
  930.  
  931.       let cookies = request.header.Cookie.split(";");
  932.       let cookieList = {};
  933.       let cookieListSorted = {};
  934.       cookies.forEach(function(cookie) {
  935.         let name, value;
  936.         [name, value] = cookie.trim().split("=");
  937.         cookieList[name] = value;
  938.       });
  939.       this._appendList("requestCookieContent", cookieList);
  940.     }
  941.   },
  942.  
  943.   /**
  944.    * Displays the request body section of the NetworkPanel and set the request
  945.    * body content on the NetworkPanel.
  946.    *
  947.    * @returns void
  948.    */
  949.   _displayRequestBody: function NP_displayRequestBody() {
  950.     this._displayNode("requestBody");
  951.     this._appendTextNode("requestBodyContent", this.httpActivity.request.body);
  952.   },
  953.  
  954.   /*
  955.    * Displays the `sent form data` section. Parses the request header for the
  956.    * submitted form data displays it inside of the `sent form data` section.
  957.    *
  958.    * @returns void
  959.    */
  960.   _displayRequestForm: function NP_processRequestForm() {
  961.     let requestBodyLines = this.httpActivity.request.body.split("\n");
  962.     let formData = requestBodyLines[requestBodyLines.length - 1].
  963.                       replace(/\+/g, " ").split("&");
  964.  
  965.     function unescapeText(aText)
  966.     {
  967.       try {
  968.         return decodeURIComponent(aText);
  969.       }
  970.       catch (ex) {
  971.         return decodeURIComponent(unescape(aText));
  972.       }
  973.     }
  974.  
  975.     let formDataObj = {};
  976.     for (let i = 0; i < formData.length; i++) {
  977.       let data = formData[i];
  978.       let idx = data.indexOf("=");
  979.       let key = data.substring(0, idx);
  980.       let value = data.substring(idx + 1);
  981.       formDataObj[unescapeText(key)] = unescapeText(value);
  982.     }
  983.  
  984.     this._appendList("requestFormDataContent", formDataObj);
  985.     this._displayNode("requestFormData");
  986.   },
  987.  
  988.   /**
  989.    * Displays the response section of the NetworkPanel, sets the response status,
  990.    * the duration between the start of the request and the receiving of the
  991.    * response header as well as the response header content on the the NetworkPanel.
  992.    *
  993.    * @returns void
  994.    */
  995.   _displayResponseHeader: function NP_displayResponseHeader()
  996.   {
  997.     let timing = this.httpActivity.timing;
  998.     let response = this.httpActivity.response;
  999.  
  1000.     this._appendTextNode("headStatus", response.status);
  1001.  
  1002.     let deltaDuration =
  1003.       Math.round((timing.RESPONSE_HEADER - timing.REQUEST_HEADER) / 1000);
  1004.     this._appendTextNode("responseHeadersInfo",
  1005.       this._format("durationMS", [deltaDuration]));
  1006.  
  1007.     this._displayNode("responseContainer");
  1008.     this._appendList("responseHeadersContent", response.header);
  1009.   },
  1010.  
  1011.   /**
  1012.    * Displays the respones image section, sets the source of the image displayed
  1013.    * in the image response section to the request URL and the duration between
  1014.    * the receiving of the response header and the end of the request. Once the
  1015.    * image is loaded, the size of the requested image is set.
  1016.    *
  1017.    * @returns void
  1018.    */
  1019.   _displayResponseImage: function NP_displayResponseImage()
  1020.   {
  1021.     let self = this;
  1022.     let timing = this.httpActivity.timing;
  1023.     let response = this.httpActivity.response;
  1024.     let cached = "";
  1025.  
  1026.     if (this._isResponseCached) {
  1027.       cached = "Cached";
  1028.     }
  1029.  
  1030.     let imageNode = this.document.getElementById("responseImage" + cached +"Node");
  1031.     imageNode.setAttribute("src", this.httpActivity.url);
  1032.  
  1033.     // This function is called to set the imageInfo.
  1034.     function setImageInfo() {
  1035.       let deltaDuration =
  1036.         Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
  1037.       self._appendTextNode("responseImage" + cached + "Info",
  1038.         self._format("imageSizeDeltaDurationMS", [
  1039.           imageNode.width, imageNode.height, deltaDuration
  1040.         ]
  1041.       ));
  1042.     }
  1043.  
  1044.     // Check if the image is already loaded.
  1045.     if (imageNode.width != 0) {
  1046.       setImageInfo();
  1047.     }
  1048.     else {
  1049.       // Image is not loaded yet therefore add a load event.
  1050.       imageNode.addEventListener("load", function imageNodeLoad() {
  1051.         imageNode.removeEventListener("load", imageNodeLoad, false);
  1052.         setImageInfo();
  1053.       }, false);
  1054.     }
  1055.  
  1056.     this._displayNode("responseImage" + cached);
  1057.   },
  1058.  
  1059.   /**
  1060.    * Displays the response body section, sets the the duration between
  1061.    * the receiving of the response header and the end of the request as well as
  1062.    * the content of the response body on the NetworkPanel.
  1063.    *
  1064.    * @param [optional] string aCachedContent
  1065.    *        Cached content for this request. If this argument is set, the
  1066.    *        responseBodyCached section is displayed.
  1067.    * @returns void
  1068.    */
  1069.   _displayResponseBody: function NP_displayResponseBody(aCachedContent)
  1070.   {
  1071.     let timing = this.httpActivity.timing;
  1072.     let response = this.httpActivity.response;
  1073.     let cached =  "";
  1074.     if (aCachedContent) {
  1075.       cached = "Cached";
  1076.     }
  1077.  
  1078.     let deltaDuration =
  1079.       Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
  1080.     this._appendTextNode("responseBody" + cached + "Info",
  1081.       this._format("durationMS", [deltaDuration]));
  1082.  
  1083.     this._displayNode("responseBody" + cached);
  1084.     this._appendTextNode("responseBody" + cached + "Content",
  1085.                             aCachedContent || response.body);
  1086.   },
  1087.  
  1088.   /**
  1089.    * Displays the `Unknown Content-Type hint` and sets the duration between the
  1090.    * receiving of the response header on the NetworkPanel.
  1091.    *
  1092.    * @returns void
  1093.    */
  1094.   _displayResponseBodyUnknownType: function NP_displayResponseBodyUnknownType()
  1095.   {
  1096.     let timing = this.httpActivity.timing;
  1097.  
  1098.     this._displayNode("responseBodyUnknownType");
  1099.     let deltaDuration =
  1100.       Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
  1101.     this._appendTextNode("responseBodyUnknownTypeInfo",
  1102.       this._format("durationMS", [deltaDuration]));
  1103.  
  1104.     this._appendTextNode("responseBodyUnknownTypeContent",
  1105.       this._format("responseBodyUnableToDisplay.content", [this._contentType]));
  1106.   },
  1107.  
  1108.   /**
  1109.    * Displays the `no response body` section and sets the the duration between
  1110.    * the receiving of the response header and the end of the request.
  1111.    *
  1112.    * @returns void
  1113.    */
  1114.   _displayNoResponseBody: function NP_displayNoResponseBody()
  1115.   {
  1116.     let timing = this.httpActivity.timing;
  1117.  
  1118.     this._displayNode("responseNoBody");
  1119.     let deltaDuration =
  1120.       Math.round((timing.RESPONSE_COMPLETE - timing.RESPONSE_HEADER) / 1000);
  1121.     this._appendTextNode("responseNoBodyInfo",
  1122.       this._format("durationMS", [deltaDuration]));
  1123.   },
  1124.  
  1125.   /*
  1126.    * Calls the isDoneCallback function if one is specified.
  1127.    */
  1128.   _callIsDone: function() {
  1129.     if (this.isDoneCallback) {
  1130.       this.isDoneCallback();
  1131.     }
  1132.   },
  1133.  
  1134.   /**
  1135.    * Updates the content of the NetworkPanel's iframe.
  1136.    *
  1137.    * @returns void
  1138.    */
  1139.   update: function NP_update()
  1140.   {
  1141.     /**
  1142.      * After the iframe's contentWindow is ready, the document object is set.
  1143.      * If the document object isn't set yet, then the page is loaded and nothing
  1144.      * can be updated.
  1145.      */
  1146.     if (!this.document) {
  1147.       return;
  1148.     }
  1149.  
  1150.     let timing = this.httpActivity.timing;
  1151.     let request = this.httpActivity.request;
  1152.     let response = this.httpActivity.response;
  1153.  
  1154.     switch (this._state) {
  1155.       case this._INIT:
  1156.         this._displayRequestHeader();
  1157.         this._state = this._DISPLAYED_REQUEST_HEADER;
  1158.         // FALL THROUGH
  1159.  
  1160.       case this._DISPLAYED_REQUEST_HEADER:
  1161.         // Process the request body if there is one.
  1162.         if (!request.bodyDiscarded && request.body) {
  1163.           // Check if we send some form data. If so, display the form data special.
  1164.           if (this._isRequestBodyFormData) {
  1165.             this._displayRequestForm();
  1166.           }
  1167.           else {
  1168.             this._displayRequestBody();
  1169.           }
  1170.           this._state = this._DISPLAYED_REQUEST_BODY;
  1171.         }
  1172.         // FALL THROUGH
  1173.  
  1174.       case this._DISPLAYED_REQUEST_BODY:
  1175.         // There is always a response header. Therefore we can skip here if
  1176.         // we don't have a response header yet and don't have to try updating
  1177.         // anything else in the NetworkPanel.
  1178.         if (!response.header) {
  1179.           break
  1180.         }
  1181.         this._displayResponseHeader();
  1182.         this._state = this._DISPLAYED_RESPONSE_HEADER;
  1183.         // FALL THROUGH
  1184.  
  1185.       case this._DISPLAYED_RESPONSE_HEADER:
  1186.         // Check if the transition is done.
  1187.         if (timing.TRANSACTION_CLOSE && response.isDone) {
  1188.           if (response.bodyDiscarded) {
  1189.             this._callIsDone();
  1190.           }
  1191.           else if (this._responseIsImage) {
  1192.             this._displayResponseImage();
  1193.             this._callIsDone();
  1194.           }
  1195.           else if (!this._isResponseBodyTextData) {
  1196.             this._displayResponseBodyUnknownType();
  1197.             this._callIsDone();
  1198.           }
  1199.           else if (response.body) {
  1200.             this._displayResponseBody();
  1201.             this._callIsDone();
  1202.           }
  1203.           else if (this._isResponseCached) {
  1204.             let self = this;
  1205.             NetworkHelper.loadFromCache(this.httpActivity.url,
  1206.                                         this.httpActivity.charset,
  1207.                                         function(aContent) {
  1208.               // If some content could be loaded from the cache, then display
  1209.               // the body.
  1210.               if (aContent) {
  1211.                 self._displayResponseBody(aContent);
  1212.                 self._callIsDone();
  1213.               }
  1214.               // Otherwise, show the "There is no response body" hint.
  1215.               else {
  1216.                 self._displayNoResponseBody();
  1217.                 self._callIsDone();
  1218.               }
  1219.             });
  1220.           }
  1221.           else {
  1222.             this._displayNoResponseBody();
  1223.             this._callIsDone();
  1224.           }
  1225.           this._state = this._TRANSITION_CLOSED;
  1226.         }
  1227.         break;
  1228.     }
  1229.   }
  1230. }
  1231.  
  1232. ///////////////////////////////////////////////////////////////////////////
  1233. //// Private utility functions for the HUD service
  1234.  
  1235. /**
  1236.  * Ensures that the number of message nodes of type aCategory don't exceed that
  1237.  * category's line limit by removing old messages as needed.
  1238.  *
  1239.  * @param aHUDId aHUDId
  1240.  *        The HeadsUpDisplay ID.
  1241.  * @param integer aCategory
  1242.  *        The category of message nodes to limit.
  1243.  * @return number
  1244.  *         The current user-selected log limit.
  1245.  */
  1246. function pruneConsoleOutputIfNecessary(aHUDId, aCategory)
  1247. {
  1248.   // Get the log limit, either from the pref or from the constant.
  1249.   let logLimit;
  1250.   try {
  1251.     let prefName = CATEGORY_CLASS_FRAGMENTS[aCategory];
  1252.     logLimit = Services.prefs.getIntPref("devtools.hud.loglimit." + prefName);
  1253.   } catch (e) {
  1254.     logLimit = DEFAULT_LOG_LIMIT;
  1255.   }
  1256.  
  1257.   let hudRef = HUDService.getHudReferenceById(aHUDId);
  1258.   let outputNode = hudRef.outputNode;
  1259.  
  1260.   let scrollBox = outputNode.scrollBoxObject.element;
  1261.   let oldScrollHeight = scrollBox.scrollHeight;
  1262.   let scrolledToBottom = ConsoleUtils.isOutputScrolledToBottom(outputNode);
  1263.  
  1264.   // Prune the nodes.
  1265.   let messageNodes = outputNode.querySelectorAll(".webconsole-msg-" +
  1266.       CATEGORY_CLASS_FRAGMENTS[aCategory]);
  1267.   let removeNodes = messageNodes.length - logLimit;
  1268.   for (let i = 0; i < removeNodes; i++) {
  1269.     if (messageNodes[i].classList.contains("webconsole-msg-cssparser")) {
  1270.       let desc = messageNodes[i].childNodes[2].textContent;
  1271.       let location = "";
  1272.       if (messageNodes[i].childNodes[4]) {
  1273.         location = messageNodes[i].childNodes[4].getAttribute("title");
  1274.       }
  1275.       delete hudRef.cssNodes[desc + location];
  1276.     }
  1277.     else if (messageNodes[i].classList.contains("webconsole-msg-inspector")) {
  1278.       hudRef.pruneConsoleDirNode(messageNodes[i]);
  1279.       continue;
  1280.     }
  1281.     messageNodes[i].parentNode.removeChild(messageNodes[i]);
  1282.   }
  1283.  
  1284.   if (!scrolledToBottom && removeNodes > 0 &&
  1285.       oldScrollHeight != scrollBox.scrollHeight) {
  1286.     scrollBox.scrollTop -= oldScrollHeight - scrollBox.scrollHeight;
  1287.   }
  1288.  
  1289.   return logLimit;
  1290. }
  1291.  
  1292. ///////////////////////////////////////////////////////////////////////////
  1293. //// The HUD service
  1294.  
  1295. function HUD_SERVICE()
  1296. {
  1297.   // TODO: provide mixins for FENNEC: bug 568621
  1298.   if (appName() == "FIREFOX") {
  1299.     var mixins = new FirefoxApplicationHooks();
  1300.   }
  1301.   else {
  1302.     throw new Error("Unsupported Application");
  1303.   }
  1304.  
  1305.   this.mixins = mixins;
  1306.  
  1307.   // These methods access the "this" object, but they're registered as
  1308.   // event listeners. So we hammer in the "this" binding.
  1309.   this.onTabClose = this.onTabClose.bind(this);
  1310.   this.onWindowUnload = this.onWindowUnload.bind(this);
  1311.  
  1312.   // Remembers the last console height, in pixels.
  1313.   this.lastConsoleHeight = Services.prefs.getIntPref("devtools.hud.height");
  1314.  
  1315.   // Network response bodies are piped through a buffer of the given size (in
  1316.   // bytes).
  1317.   this.responsePipeSegmentSize =
  1318.     Services.prefs.getIntPref("network.buffer.cache.size");
  1319.  
  1320.   /**
  1321.    * Collection of HUDIds that map to the tabs/windows/contexts
  1322.    * that a HeadsUpDisplay can be activated for.
  1323.    */
  1324.   this.activatedContexts = [];
  1325.  
  1326.   /**
  1327.    * Collection of outer window IDs mapping to HUD IDs.
  1328.    */
  1329.   this.windowIds = {};
  1330.  
  1331.   /**
  1332.    * Each HeadsUpDisplay has a set of filter preferences
  1333.    */
  1334.   this.filterPrefs = {};
  1335.  
  1336.   /**
  1337.    * Keeps a reference for each HeadsUpDisplay that is created
  1338.    */
  1339.   this.hudReferences = {};
  1340.  
  1341.   /**
  1342.    * Requests that haven't finished yet.
  1343.    */
  1344.   this.openRequests = {};
  1345.  
  1346.   /**
  1347.    * Response headers for requests that haven't finished yet.
  1348.    */
  1349.   this.openResponseHeaders = {};
  1350. };
  1351.  
  1352. HUD_SERVICE.prototype =
  1353. {
  1354.   /**
  1355.    * L10N shortcut function
  1356.    *
  1357.    * @param string aName
  1358.    * @returns string
  1359.    */
  1360.   getStr: function HS_getStr(aName)
  1361.   {
  1362.     return stringBundle.GetStringFromName(aName);
  1363.   },
  1364.  
  1365.   /**
  1366.    * L10N shortcut function
  1367.    *
  1368.    * @param string aName
  1369.    * @returns (format) string
  1370.    */
  1371.   getFormatStr: function HS_getFormatStr(aName, aArray)
  1372.   {
  1373.     return stringBundle.formatStringFromName(aName, aArray, aArray.length);
  1374.   },
  1375.  
  1376.   /**
  1377.    * getter for UI commands to be used by the frontend
  1378.    *
  1379.    * @returns object
  1380.    */
  1381.   get consoleUI() {
  1382.     return HeadsUpDisplayUICommands;
  1383.   },
  1384.  
  1385.   /**
  1386.    * The sequencer is a generator (after initialization) that returns unique
  1387.    * integers
  1388.    */
  1389.   sequencer: null,
  1390.  
  1391.   /**
  1392.    * Gets the ID of the outer window of this DOM window
  1393.    *
  1394.    * @param nsIDOMWindow aWindow
  1395.    * @returns integer
  1396.    */
  1397.   getWindowId: function HS_getWindowId(aWindow)
  1398.   {
  1399.     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  1400.   },
  1401.  
  1402.   /**
  1403.    * Gets the top level content window that has an outer window with
  1404.    * the given ID or returns null if no such content window exists
  1405.    *
  1406.    * @param integer aId
  1407.    * @returns nsIDOMWindow
  1408.    */
  1409.   getWindowByWindowId: function HS_getWindowByWindowId(aId)
  1410.   {
  1411.     // In the future (post-Electrolysis), getOuterWindowWithId() could
  1412.     // return null, because the originating window could have gone away
  1413.     // while we were in the process of receiving and/or processing a
  1414.     // message. For future-proofing purposes, we do a null check here.
  1415.  
  1416.     let someWindow = Services.wm.getMostRecentWindow(null);
  1417.     let content = null;
  1418.  
  1419.     if (someWindow) {
  1420.       let windowUtils = someWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  1421.                                   .getInterface(Ci.nsIDOMWindowUtils);
  1422.       content = windowUtils.getOuterWindowWithId(aId);
  1423.     }
  1424.  
  1425.     return content;
  1426.   },
  1427.  
  1428.   /**
  1429.    * Whether to save the bodies of network requests and responses. Disabled by
  1430.    * default to save memory.
  1431.    */
  1432.   saveRequestAndResponseBodies: false,
  1433.  
  1434.   /**
  1435.    * Tell the HUDService that a HeadsUpDisplay can be activated
  1436.    * for the window or context that has 'aContextDOMId' node id
  1437.    *
  1438.    * @param string aContextDOMId
  1439.    * @return void
  1440.    */
  1441.   registerActiveContext: function HS_registerActiveContext(aContextDOMId)
  1442.   {
  1443.     this.activatedContexts.push(aContextDOMId);
  1444.   },
  1445.  
  1446.   /**
  1447.    * Firefox-specific current tab getter
  1448.    *
  1449.    * @returns nsIDOMWindow
  1450.    */
  1451.   currentContext: function HS_currentContext() {
  1452.     return this.mixins.getCurrentContext();
  1453.   },
  1454.  
  1455.   /**
  1456.    * Tell the HUDService that a HeadsUpDisplay should be deactivated
  1457.    *
  1458.    * @param string aContextDOMId
  1459.    * @return void
  1460.    */
  1461.   unregisterActiveContext: function HS_deregisterActiveContext(aContextDOMId)
  1462.   {
  1463.     var domId = aContextDOMId.split("_")[1];
  1464.     var idx = this.activatedContexts.indexOf(domId);
  1465.     if (idx > -1) {
  1466.       this.activatedContexts.splice(idx, 1);
  1467.     }
  1468.   },
  1469.  
  1470.   /**
  1471.    * Tells callers that a HeadsUpDisplay can be activated for the context
  1472.    *
  1473.    * @param string aContextDOMId
  1474.    * @return boolean
  1475.    */
  1476.   canActivateContext: function HS_canActivateContext(aContextDOMId)
  1477.   {
  1478.     var domId = aContextDOMId.split("_")[1];
  1479.     for (var idx in this.activatedContexts) {
  1480.       if (this.activatedContexts[idx] == domId){
  1481.         return true;
  1482.       }
  1483.     }
  1484.     return false;
  1485.   },
  1486.  
  1487.   /**
  1488.    * Activate a HeadsUpDisplay for the given tab context.
  1489.    *
  1490.    * @param Element aContext the tab element.
  1491.    * @param boolean aAnimated animate opening the Web Console?
  1492.    * @returns void
  1493.    */
  1494.   activateHUDForContext: function HS_activateHUDForContext(aContext, aAnimated)
  1495.   {
  1496.     this.wakeup();
  1497.  
  1498.     let window = aContext.linkedBrowser.contentWindow;
  1499.     let nBox = aContext.ownerDocument.defaultView.
  1500.       getNotificationBox(window);
  1501.     this.registerActiveContext(nBox.id);
  1502.     this.windowInitializer(window);
  1503.  
  1504.     let hudId = "hud_" + nBox.id;
  1505.     let hudRef = this.hudReferences[hudId];
  1506.  
  1507.     if (!aAnimated || hudRef.consolePanel) {
  1508.       this.disableAnimation(hudId);
  1509.     }
  1510.  
  1511.     // Create a processing instruction for GCLIs CSS stylesheet, but only if
  1512.     // we don't have one for this document. Also record the context we're
  1513.     // adding this for so we know when to remove it.
  1514.     let procInstr = aContext.ownerDocument.gcliCssProcInstr;
  1515.     if (!procInstr) {
  1516.       procInstr = aContext.ownerDocument.createProcessingInstruction(
  1517.               "xml-stylesheet",
  1518.               "href='chrome://browser/skin/devtools/gcli.css' type='text/css'");
  1519.       procInstr.contexts = [];
  1520.  
  1521.       let root = aContext.ownerDocument.getElementsByTagName('window')[0];
  1522.       root.parentNode.insertBefore(procInstr, root);
  1523.       aContext.ownerDocument.gcliCssProcInstr = procInstr;
  1524.     }
  1525.     if (procInstr.contexts.indexOf(hudId) == -1) {
  1526.       procInstr.contexts.push(hudId);
  1527.     }
  1528.   },
  1529.  
  1530.   /**
  1531.    * Deactivate a HeadsUpDisplay for the given tab context.
  1532.    *
  1533.    * @param nsIDOMWindow aContext
  1534.    * @param aAnimated animate closing the web console?
  1535.    * @returns void
  1536.    */
  1537.   deactivateHUDForContext: function HS_deactivateHUDForContext(aContext, aAnimated)
  1538.   {
  1539.     let browser = aContext.linkedBrowser;
  1540.     let window = browser.contentWindow;
  1541.     let chromeDocument = aContext.ownerDocument;
  1542.     let nBox = chromeDocument.defaultView.getNotificationBox(window);
  1543.     let hudId = "hud_" + nBox.id;
  1544.     let displayNode = chromeDocument.getElementById(hudId);
  1545.  
  1546.     if (hudId in this.hudReferences && displayNode) {
  1547.       if (!aAnimated) {
  1548.         this.storeHeight(hudId);
  1549.       }
  1550.  
  1551.       let hud = this.hudReferences[hudId];
  1552.       browser.webProgress.removeProgressListener(hud.progressListener);
  1553.       delete hud.progressListener;
  1554.  
  1555.       this.unregisterDisplay(hudId);
  1556.  
  1557.       window.focus();
  1558.     }
  1559.  
  1560.     // Remove this context from the list of contexts that need the GCLI CSS
  1561.     // processing instruction and then remove the processing instruction if it
  1562.     // isn't needed any more.
  1563.     let procInstr = aContext.ownerDocument.gcliCssProcInstr;
  1564.     if (procInstr) {
  1565.       procInstr.contexts = procInstr.contexts.filter(function(id) {
  1566.         return id !== hudId;
  1567.       });
  1568.       if (procInstr.contexts.length == 0 && procInstr.parentNode) {
  1569.         procInstr.parentNode.removeChild(procInstr);
  1570.         delete aContext.ownerDocument.gcliCssProcInstr;
  1571.       }
  1572.     }
  1573.   },
  1574.  
  1575.   /**
  1576.    * get a unique ID from the sequence generator
  1577.    *
  1578.    * @returns integer
  1579.    */
  1580.   sequenceId: function HS_sequencerId()
  1581.   {
  1582.     if (!this.sequencer) {
  1583.       this.sequencer = this.createSequencer(-1);
  1584.     }
  1585.     return this.sequencer.next();
  1586.   },
  1587.  
  1588.   /**
  1589.    * get the default filter prefs
  1590.    *
  1591.    * @param string aHUDId
  1592.    * @returns JS Object
  1593.    */
  1594.   getDefaultFilterPrefs: function HS_getDefaultFilterPrefs(aHUDId) {
  1595.     return this.filterPrefs[aHUDId];
  1596.   },
  1597.  
  1598.   /**
  1599.    * get the current filter prefs
  1600.    *
  1601.    * @param string aHUDId
  1602.    * @returns JS Object
  1603.    */
  1604.   getFilterPrefs: function HS_getFilterPrefs(aHUDId) {
  1605.     return this.filterPrefs[aHUDId];
  1606.   },
  1607.  
  1608.   /**
  1609.    * get the filter state for a specific toggle button on a heads up display
  1610.    *
  1611.    * @param string aHUDId
  1612.    * @param string aToggleType
  1613.    * @returns boolean
  1614.    */
  1615.   getFilterState: function HS_getFilterState(aHUDId, aToggleType)
  1616.   {
  1617.     if (!aHUDId) {
  1618.       return false;
  1619.     }
  1620.     try {
  1621.       var bool = this.filterPrefs[aHUDId][aToggleType];
  1622.       return bool;
  1623.     }
  1624.     catch (ex) {
  1625.       return false;
  1626.     }
  1627.   },
  1628.  
  1629.   /**
  1630.    * set the filter state for a specific toggle button on a heads up display
  1631.    *
  1632.    * @param string aHUDId
  1633.    * @param string aToggleType
  1634.    * @param boolean aState
  1635.    * @returns void
  1636.    */
  1637.   setFilterState: function HS_setFilterState(aHUDId, aToggleType, aState)
  1638.   {
  1639.     this.filterPrefs[aHUDId][aToggleType] = aState;
  1640.     this.adjustVisibilityForMessageType(aHUDId, aToggleType, aState);
  1641.   },
  1642.  
  1643.   /**
  1644.    * Splits the given console messages into groups based on their timestamps.
  1645.    *
  1646.    * @param nsIDOMNode aOutputNode
  1647.    *        The output node to alter.
  1648.    * @returns void
  1649.    */
  1650.   regroupOutput: function HS_regroupOutput(aOutputNode)
  1651.   {
  1652.     // Go through the nodes and adjust the placement of "webconsole-new-group"
  1653.     // classes.
  1654.  
  1655.     let nodes = aOutputNode.querySelectorAll(".hud-msg-node" +
  1656.       ":not(.hud-filtered-by-string):not(.hud-filtered-by-type)");
  1657.     let lastTimestamp;
  1658.     for (let i = 0; i < nodes.length; i++) {
  1659.       let thisTimestamp = nodes[i].timestamp;
  1660.       if (lastTimestamp != null &&
  1661.           thisTimestamp >= lastTimestamp + NEW_GROUP_DELAY) {
  1662.         nodes[i].classList.add("webconsole-new-group");
  1663.       }
  1664.       else {
  1665.         nodes[i].classList.remove("webconsole-new-group");
  1666.       }
  1667.       lastTimestamp = thisTimestamp;
  1668.     }
  1669.   },
  1670.  
  1671.   /**
  1672.    * Turns the display of log nodes on and off appropriately to reflect the
  1673.    * adjustment of the message type filter named by @aPrefKey.
  1674.    *
  1675.    * @param string aHUDId
  1676.    *        The ID of the HUD to alter.
  1677.    * @param string aPrefKey
  1678.    *        The preference key for the message type being filtered: one of the
  1679.    *        values in the MESSAGE_PREFERENCE_KEYS table.
  1680.    * @param boolean aState
  1681.    *        True if the filter named by @aMessageType is being turned on; false
  1682.    *        otherwise.
  1683.    * @returns void
  1684.    */
  1685.   adjustVisibilityForMessageType:
  1686.   function HS_adjustVisibilityForMessageType(aHUDId, aPrefKey, aState)
  1687.   {
  1688.     let outputNode = this.getHudReferenceById(aHUDId).outputNode;
  1689.     let doc = outputNode.ownerDocument;
  1690.  
  1691.     // Look for message nodes ("hud-msg-node") with the given preference key
  1692.     // ("hud-msg-error", "hud-msg-cssparser", etc.) and add or remove the
  1693.     // "hud-filtered-by-type" class, which turns on or off the display.
  1694.  
  1695.     let xpath = ".//*[contains(@class, 'hud-msg-node') and " +
  1696.       "contains(concat(@class, ' '), 'hud-" + aPrefKey + " ')]";
  1697.     let result = doc.evaluate(xpath, outputNode, null,
  1698.       Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  1699.     for (let i = 0; i < result.snapshotLength; i++) {
  1700.       let node = result.snapshotItem(i);
  1701.       if (aState) {
  1702.         node.classList.remove("hud-filtered-by-type");
  1703.       }
  1704.       else {
  1705.         node.classList.add("hud-filtered-by-type");
  1706.       }
  1707.     }
  1708.  
  1709.     this.regroupOutput(outputNode);
  1710.   },
  1711.  
  1712.   /**
  1713.    * Check that the passed string matches the filter arguments.
  1714.    *
  1715.    * @param String aString
  1716.    *        to search for filter words in.
  1717.    * @param String aFilter
  1718.    *        is a string containing all of the words to filter on.
  1719.    * @returns boolean
  1720.    */
  1721.   stringMatchesFilters: function stringMatchesFilters(aString, aFilter)
  1722.   {
  1723.     if (!aFilter || !aString) {
  1724.       return true;
  1725.     }
  1726.  
  1727.     let searchStr = aString.toLowerCase();
  1728.     let filterStrings = aFilter.toLowerCase().split(/\s+/);
  1729.     return !filterStrings.some(function (f) {
  1730.       return searchStr.indexOf(f) == -1;
  1731.     });
  1732.   },
  1733.  
  1734.   /**
  1735.    * Turns the display of log nodes on and off appropriately to reflect the
  1736.    * adjustment of the search string.
  1737.    *
  1738.    * @param string aHUDId
  1739.    *        The ID of the HUD to alter.
  1740.    * @param string aSearchString
  1741.    *        The new search string.
  1742.    * @returns void
  1743.    */
  1744.   adjustVisibilityOnSearchStringChange:
  1745.   function HS_adjustVisibilityOnSearchStringChange(aHUDId, aSearchString)
  1746.   {
  1747.     let outputNode = this.getHudReferenceById(aHUDId).outputNode;
  1748.  
  1749.     let nodes = outputNode.querySelectorAll(".hud-msg-node");
  1750.  
  1751.     for (let i = 0; i < nodes.length; ++i) {
  1752.       let node = nodes[i];
  1753.  
  1754.       // hide nodes that match the strings
  1755.       let text = node.clipboardText;
  1756.  
  1757.       // if the text matches the words in aSearchString...
  1758.       if (this.stringMatchesFilters(text, aSearchString)) {
  1759.         node.classList.remove("hud-filtered-by-string");
  1760.       }
  1761.       else {
  1762.         node.classList.add("hud-filtered-by-string");
  1763.       }
  1764.     }
  1765.  
  1766.     this.regroupOutput(outputNode);
  1767.   },
  1768.  
  1769.   /**
  1770.    * Register a reference of each HeadsUpDisplay that is created
  1771.    */
  1772.   registerHUDReference:
  1773.   function HS_registerHUDReference(aHUD)
  1774.   {
  1775.     this.hudReferences[aHUD.hudId] = aHUD;
  1776.  
  1777.     let id = ConsoleUtils.supString(aHUD.hudId);
  1778.     Services.obs.notifyObservers(id, "web-console-created", null);
  1779.   },
  1780.  
  1781.   /**
  1782.    * Register a new Heads Up Display
  1783.    *
  1784.    * @returns void
  1785.    */
  1786.   registerDisplay: function HS_registerDisplay(aHUDId)
  1787.   {
  1788.     // register a display DOM node Id with the service.
  1789.     if (!aHUDId){
  1790.       throw new Error(ERRORS.MISSING_ARGS);
  1791.     }
  1792.     this.filterPrefs[aHUDId] = this.defaultFilterPrefs;
  1793.     // init storage objects:
  1794.     this.storage.createDisplay(aHUDId);
  1795.   },
  1796.  
  1797.   /**
  1798.    * When a display is being destroyed, unregister it first
  1799.    *
  1800.    * @param string aHUDId
  1801.    *        The ID of a HUD.
  1802.    * @returns void
  1803.    */
  1804.   unregisterDisplay: function HS_unregisterDisplay(aHUDId)
  1805.   {
  1806.     let hud = this.getHudReferenceById(aHUDId);
  1807.  
  1808.     // Remove children from the output. If the output is not cleared, there can
  1809.     // be leaks as some nodes has node.onclick = function; set and GC can't
  1810.     // remove the nodes then.
  1811.     if (hud.jsterm) {
  1812.       hud.jsterm.clearOutput();
  1813.     }
  1814.     if (hud.gcliterm) {
  1815.       hud.gcliterm.clearOutput();
  1816.     }
  1817.  
  1818.     hud.destroy();
  1819.  
  1820.     // Make sure that the console panel does not try to call
  1821.     // deactivateHUDForContext() again.
  1822.     hud.consoleWindowUnregisterOnHide = false;
  1823.  
  1824.     // Remove the HUDBox and the consolePanel if the Web Console is inside a
  1825.     // floating panel.
  1826.     if (hud.consolePanel && hud.consolePanel.parentNode) {
  1827.       hud.consolePanel.parentNode.removeChild(hud.consolePanel);
  1828.       hud.consolePanel.removeAttribute("hudId");
  1829.       hud.consolePanel = null;
  1830.     }
  1831.  
  1832.     hud.HUDBox.parentNode.removeChild(hud.HUDBox);
  1833.  
  1834.     if (hud.splitter.parentNode) {
  1835.       hud.splitter.parentNode.removeChild(hud.splitter);
  1836.     }
  1837.  
  1838.     if (hud.jsterm) {
  1839.       hud.jsterm.autocompletePopup.destroy();
  1840.     }
  1841.  
  1842.     delete this.hudReferences[aHUDId];
  1843.  
  1844.     // remove the related storage object
  1845.     this.storage.removeDisplay(aHUDId);
  1846.  
  1847.     for (let windowID in this.windowIds) {
  1848.       if (this.windowIds[windowID] == aHUDId) {
  1849.         delete this.windowIds[windowID];
  1850.       }
  1851.     }
  1852.  
  1853.     this.unregisterActiveContext(aHUDId);
  1854.  
  1855.     let popupset = hud.chromeDocument.getElementById("mainPopupSet");
  1856.     let panels = popupset.querySelectorAll("panel[hudId=" + aHUDId + "]");
  1857.     for (let i = 0; i < panels.length; i++) {
  1858.       panels[i].hidePopup();
  1859.     }
  1860.  
  1861.     let id = ConsoleUtils.supString(aHUDId);
  1862.     Services.obs.notifyObservers(id, "web-console-destroyed", null);
  1863.  
  1864.     if (Object.keys(this.hudReferences).length == 0) {
  1865.       let autocompletePopup = hud.chromeDocument.
  1866.                               getElementById("webConsole_autocompletePopup");
  1867.       if (autocompletePopup) {
  1868.         autocompletePopup.parentNode.removeChild(autocompletePopup);
  1869.       }
  1870.  
  1871.       this.suspend();
  1872.     }
  1873.   },
  1874.  
  1875.   /**
  1876.    * "Wake up" the Web Console activity. This is called when the first Web
  1877.    * Console is open. This method initializes the various observers we have.
  1878.    *
  1879.    * @returns void
  1880.    */
  1881.   wakeup: function HS_wakeup()
  1882.   {
  1883.     if (Object.keys(this.hudReferences).length > 0) {
  1884.       return;
  1885.     }
  1886.  
  1887.     this.storage = new ConsoleStorage();
  1888.     this.defaultFilterPrefs = this.storage.defaultDisplayPrefs;
  1889.     this.defaultGlobalConsolePrefs = this.storage.defaultGlobalConsolePrefs;
  1890.  
  1891.     // begin observing HTTP traffic
  1892.     this.startHTTPObservation();
  1893.  
  1894.     HUDWindowObserver.init();
  1895.     HUDConsoleObserver.init();
  1896.     ConsoleAPIObserver.init();
  1897.   },
  1898.  
  1899.   /**
  1900.    * Suspend Web Console activity. This is called when all Web Consoles are
  1901.    * closed.
  1902.    *
  1903.    * @returns void
  1904.    */
  1905.   suspend: function HS_suspend()
  1906.   {
  1907.     activityDistributor.removeObserver(this.httpObserver);
  1908.     delete this.httpObserver;
  1909.  
  1910.     Services.obs.removeObserver(this.httpResponseExaminer,
  1911.                                 "http-on-examine-response");
  1912.  
  1913.     this.openRequests = {};
  1914.     this.openResponseHeaders = {};
  1915.  
  1916.     // delete the storage as it holds onto channels
  1917.     delete this.storage;
  1918.     delete this.defaultFilterPrefs;
  1919.     delete this.defaultGlobalConsolePrefs;
  1920.  
  1921.     delete this.lastFinishedRequestCallback;
  1922.  
  1923.     HUDWindowObserver.uninit();
  1924.     HUDConsoleObserver.uninit();
  1925.     ConsoleAPIObserver.shutdown();
  1926.   },
  1927.  
  1928.   /**
  1929.    * Shutdown all HeadsUpDisplays on xpcom-shutdown
  1930.    *
  1931.    * @returns void
  1932.    */
  1933.   shutdown: function HS_shutdown()
  1934.   {
  1935.     for (let hudId in this.hudReferences) {
  1936.       this.deactivateHUDForContext(this.hudReferences[hudId].tab, false);
  1937.     }
  1938.   },
  1939.  
  1940.   /**
  1941.    * Returns the HeadsUpDisplay object associated to a content window.
  1942.    *
  1943.    * @param nsIDOMWindow aContentWindow
  1944.    * @returns object
  1945.    */
  1946.   getHudByWindow: function HS_getHudByWindow(aContentWindow)
  1947.   {
  1948.     let hudId = this.getHudIdByWindow(aContentWindow);
  1949.     return hudId ? this.hudReferences[hudId] : null;
  1950.   },
  1951.  
  1952.   /**
  1953.    * Returns the hudId that is corresponding to the hud activated for the
  1954.    * passed aContentWindow. If there is no matching hudId null is returned.
  1955.    *
  1956.    * @param nsIDOMWindow aContentWindow
  1957.    * @returns string or null
  1958.    */
  1959.   getHudIdByWindow: function HS_getHudIdByWindow(aContentWindow)
  1960.   {
  1961.     let windowId = this.getWindowId(aContentWindow);
  1962.     return this.getHudIdByWindowId(windowId);
  1963.   },
  1964.  
  1965.   /**
  1966.    * Returns the hudReference for a given id.
  1967.    *
  1968.    * @param string aId
  1969.    * @returns Object
  1970.    */
  1971.   getHudReferenceById: function HS_getHudReferenceById(aId)
  1972.   {
  1973.     return aId in this.hudReferences ? this.hudReferences[aId] : null;
  1974.   },
  1975.  
  1976.   /**
  1977.    * Returns the hudId that is corresponding to the given outer window ID.
  1978.    *
  1979.    * @param number aWindowId
  1980.    *        the outer window ID
  1981.    * @returns string the hudId
  1982.    */
  1983.   getHudIdByWindowId: function HS_getHudIdByWindowId(aWindowId)
  1984.   {
  1985.     return this.windowIds[aWindowId];
  1986.   },
  1987.  
  1988.   /**
  1989.    * Get the current filter string for the HeadsUpDisplay
  1990.    *
  1991.    * @param string aHUDId
  1992.    * @returns string
  1993.    */
  1994.   getFilterStringByHUDId: function HS_getFilterStringbyHUDId(aHUDId) {
  1995.     return this.getHudReferenceById(aHUDId).filterBox.value;
  1996.   },
  1997.  
  1998.   /**
  1999.    * Update the filter text in the internal tracking object for all
  2000.    * filter strings
  2001.    *
  2002.    * @param nsIDOMNode aTextBoxNode
  2003.    * @returns void
  2004.    */
  2005.   updateFilterText: function HS_updateFiltertext(aTextBoxNode)
  2006.   {
  2007.     var hudId = aTextBoxNode.getAttribute("hudId");
  2008.     this.adjustVisibilityOnSearchStringChange(hudId, aTextBoxNode.value);
  2009.   },
  2010.  
  2011.   /**
  2012.    * Logs a message to the Web Console that originates from the window.console
  2013.    * service ("console-api-log-event" notifications).
  2014.    *
  2015.    * @param string aHUDId
  2016.    *        The ID of the Web Console to which to send the message.
  2017.    * @param object aMessage
  2018.    *        The message reported by the console service.
  2019.    * @return void
  2020.    */
  2021.   logConsoleAPIMessage: function HS_logConsoleAPIMessage(aHUDId, aMessage)
  2022.   {
  2023.     // Pipe the message to createMessageNode().
  2024.     let hud = HUDService.hudReferences[aHUDId];
  2025.     function formatResult(x) {
  2026.       return (typeof(x) == "string") ? x : hud.jsterm.formatResult(x);
  2027.     }
  2028.  
  2029.     let body = null;
  2030.     let clipboardText = null;
  2031.     let sourceURL = null;
  2032.     let sourceLine = 0;
  2033.     let level = aMessage.level;
  2034.     let args = aMessage.arguments;
  2035.  
  2036.     switch (level) {
  2037.       case "log":
  2038.       case "info":
  2039.       case "warn":
  2040.       case "error":
  2041.       case "debug":
  2042.         let mappedArguments = Array.map(args, formatResult);
  2043.         body = Array.join(mappedArguments, " ");
  2044.         sourceURL = aMessage.filename;
  2045.         sourceLine = aMessage.lineNumber;
  2046.         break;
  2047.  
  2048.       case "trace":
  2049.         let filename = ConsoleUtils.abbreviateSourceURL(args[0].filename);
  2050.         let functionName = args[0].functionName ||
  2051.                            this.getStr("stacktrace.anonymousFunction");
  2052.         let lineNumber = args[0].lineNumber;
  2053.  
  2054.         body = this.getFormatStr("stacktrace.outputMessage",
  2055.                                  [filename, functionName, lineNumber]);
  2056.  
  2057.         sourceURL = args[0].filename;
  2058.         sourceLine = args[0].lineNumber;
  2059.  
  2060.         clipboardText = "";
  2061.  
  2062.         args.forEach(function(aFrame) {
  2063.           clipboardText += aFrame.filename + " :: " +
  2064.                            aFrame.functionName + " :: " +
  2065.                            aFrame.lineNumber + "\n";
  2066.         });
  2067.  
  2068.         clipboardText = clipboardText.trimRight();
  2069.         break;
  2070.  
  2071.       case "dir":
  2072.         body = unwrap(args[0]);
  2073.         clipboardText = body.toString();
  2074.         sourceURL = aMessage.filename;
  2075.         sourceLine = aMessage.lineNumber;
  2076.         break;
  2077.  
  2078.       case "group":
  2079.       case "groupCollapsed":
  2080.         clipboardText = body = formatResult(args);
  2081.         sourceURL = aMessage.filename;
  2082.         sourceLine = aMessage.lineNumber;
  2083.         hud.groupDepth++;
  2084.         break;
  2085.  
  2086.       case "groupEnd":
  2087.         if (hud.groupDepth > 0) {
  2088.           hud.groupDepth--;
  2089.         }
  2090.         return;
  2091.  
  2092.       case "time":
  2093.         if (!args) {
  2094.           return;
  2095.         }
  2096.         if (args.error) {
  2097.           Cu.reportError(this.getStr(args.error));
  2098.           return;
  2099.         }
  2100.         body = this.getFormatStr("timerStarted", [args.name]);
  2101.         clipboardText = body;
  2102.         sourceURL = aMessage.filename;
  2103.         sourceLine = aMessage.lineNumber;
  2104.         break;
  2105.  
  2106.       case "timeEnd":
  2107.         if (!args) {
  2108.           return;
  2109.         }
  2110.         body = this.getFormatStr("timeEnd", [args.name, args.duration]);
  2111.         clipboardText = body;
  2112.         sourceURL = aMessage.filename;
  2113.         sourceLine = aMessage.lineNumber;
  2114.         break;
  2115.  
  2116.       default:
  2117.         Cu.reportError("Unknown Console API log level: " + level);
  2118.         return;
  2119.     }
  2120.  
  2121.     let node = ConsoleUtils.createMessageNode(hud.outputNode.ownerDocument,
  2122.                                               CATEGORY_WEBDEV,
  2123.                                               LEVELS[level],
  2124.                                               body,
  2125.                                               aHUDId,
  2126.                                               sourceURL,
  2127.                                               sourceLine,
  2128.                                               clipboardText,
  2129.                                               level);
  2130.  
  2131.     // Make the node bring up the property panel, to allow the user to inspect
  2132.     // the stack trace.
  2133.     if (level == "trace") {
  2134.       node._stacktrace = args;
  2135.  
  2136.       let linkNode = node.querySelector(".webconsole-msg-body");
  2137.       linkNode.classList.add("hud-clickable");
  2138.       linkNode.setAttribute("aria-haspopup", "true");
  2139.  
  2140.       node.addEventListener("mousedown", function(aEvent) {
  2141.         this._startX = aEvent.clientX;
  2142.         this._startY = aEvent.clientY;
  2143.       }, false);
  2144.  
  2145.       node.addEventListener("click", function(aEvent) {
  2146.         if (aEvent.detail != 1 || aEvent.button != 0 ||
  2147.             (this._startX != aEvent.clientX &&
  2148.              this._startY != aEvent.clientY)) {
  2149.           return;
  2150.         }
  2151.  
  2152.         if (!this._panelOpen) {
  2153.           let propPanel = hud.jsterm.openPropertyPanel(null,
  2154.                                                        node._stacktrace,
  2155.                                                        this);
  2156.           propPanel.panel.setAttribute("hudId", aHUDId);
  2157.           this._panelOpen = true;
  2158.         }
  2159.       }, false);
  2160.     }
  2161.  
  2162.     ConsoleUtils.outputMessageNode(node, aHUDId);
  2163.  
  2164.     if (level == "dir") {
  2165.       // Initialize the inspector message node, by setting the PropertyTreeView
  2166.       // object on the tree view. This has to be done *after* the node is
  2167.       // shown, because the tree binding must be attached first.
  2168.       let tree = node.querySelector("tree");
  2169.       tree.view = node.propertyTreeView;
  2170.     }
  2171.   },
  2172.  
  2173.   /**
  2174.    * Inform user that the Web Console API has been replaced by a script
  2175.    * in a content page.
  2176.    *
  2177.    * @param string aHUDId
  2178.    *        The ID of the Web Console to which to send the message.
  2179.    * @return void
  2180.    */
  2181.   logWarningAboutReplacedAPI:
  2182.   function HS_logWarningAboutReplacedAPI(aHUDId)
  2183.   {
  2184.     let hud = this.hudReferences[aHUDId];
  2185.     let chromeDocument = hud.HUDBox.ownerDocument;
  2186.     let message = stringBundle.GetStringFromName("ConsoleAPIDisabled");
  2187.     let node = ConsoleUtils.createMessageNode(chromeDocument, CATEGORY_JS,
  2188.                                               SEVERITY_WARNING, message,
  2189.                                               aHUDId);
  2190.     ConsoleUtils.outputMessageNode(node, aHUDId);
  2191.   },
  2192.  
  2193.   /**
  2194.    * Reports an error in the page source, either JavaScript or CSS.
  2195.    *
  2196.    * @param number aCategory
  2197.    *        The category of the message; either CATEGORY_CSS or CATEGORY_JS.
  2198.    * @param nsIScriptError aScriptError
  2199.    *        The error message to report.
  2200.    * @return void
  2201.    */
  2202.   reportPageError: function HS_reportPageError(aCategory, aScriptError)
  2203.   {
  2204.     if (aCategory != CATEGORY_CSS && aCategory != CATEGORY_JS) {
  2205.       throw Components.Exception("Unsupported category (must be one of CSS " +
  2206.                                  "or JS)", Cr.NS_ERROR_INVALID_ARG,
  2207.                                  Components.stack.caller);
  2208.     }
  2209.  
  2210.     // Warnings and legacy strict errors become warnings; other types become
  2211.     // errors.
  2212.     let severity = SEVERITY_ERROR;
  2213.     if ((aScriptError.flags & aScriptError.warningFlag) ||
  2214.         (aScriptError.flags & aScriptError.strictFlag)) {
  2215.       severity = SEVERITY_WARNING;
  2216.     }
  2217.  
  2218.     let window = HUDService.getWindowByWindowId(aScriptError.outerWindowID);
  2219.     if (window) {
  2220.       let hudId = HUDService.getHudIdByWindow(window.top);
  2221.       if (hudId) {
  2222.         let outputNode = this.hudReferences[hudId].outputNode;
  2223.         let chromeDocument = outputNode.ownerDocument;
  2224.  
  2225.         let node = ConsoleUtils.createMessageNode(chromeDocument,
  2226.                                                   aCategory,
  2227.                                                   severity,
  2228.                                                   aScriptError.errorMessage,
  2229.                                                   hudId,
  2230.                                                   aScriptError.sourceName,
  2231.                                                   aScriptError.lineNumber);
  2232.  
  2233.         ConsoleUtils.outputMessageNode(node, hudId);
  2234.       }
  2235.     }
  2236.   },
  2237.  
  2238.   /**
  2239.    * Register a Gecko app's specialized ApplicationHooks object
  2240.    *
  2241.    * @returns void or throws "UNSUPPORTED APPLICATION" error
  2242.    */
  2243.   registerApplicationHooks:
  2244.   function HS_registerApplications(aAppName, aHooksObject)
  2245.   {
  2246.     switch(aAppName) {
  2247.       case "FIREFOX":
  2248.         this.applicationHooks = aHooksObject;
  2249.         return;
  2250.       default:
  2251.         throw new Error("MOZ APPLICATION UNSUPPORTED");
  2252.     }
  2253.   },
  2254.  
  2255.   /**
  2256.    * Registry of ApplicationHooks used by specified Gecko Apps
  2257.    *
  2258.    * @returns Specific Gecko 'ApplicationHooks' Object/Mixin
  2259.    */
  2260.   applicationHooks: null,
  2261.  
  2262.   /**
  2263.    * Assign a function to this property to listen for finished httpRequests.
  2264.    * Used by unit tests.
  2265.    */
  2266.   lastFinishedRequestCallback: null,
  2267.  
  2268.   /**
  2269.    * Opens a NetworkPanel.
  2270.    *
  2271.    * @param nsIDOMNode aNode
  2272.    *        DOMNode to display the panel next to.
  2273.    * @param object aHttpActivity
  2274.    *        httpActivity object. The data of this object is displayed in the
  2275.    *        NetworkPanel.
  2276.    * @returns NetworkPanel
  2277.    */
  2278.   openNetworkPanel: function HS_openNetworkPanel(aNode, aHttpActivity)
  2279.   {
  2280.     let doc = aNode.ownerDocument;
  2281.     let parent = doc.getElementById("mainPopupSet");
  2282.     let netPanel = new NetworkPanel(parent, aHttpActivity);
  2283.     netPanel.linkNode = aNode;
  2284.  
  2285.     let panel = netPanel.panel;
  2286.     panel.openPopup(aNode, "after_pointer", 0, 0, false, false);
  2287.     panel.sizeTo(450, 500);
  2288.     panel.setAttribute("hudId", aHttpActivity.hudId);
  2289.     aHttpActivity.panels.push(Cu.getWeakReference(netPanel));
  2290.     return netPanel;
  2291.   },
  2292.  
  2293.   /**
  2294.    * Begin observing HTTP traffic that we care about,
  2295.    * namely traffic that originates inside any context that a Heads Up Display
  2296.    * is active for.
  2297.    */
  2298.   startHTTPObservation: function HS_httpObserverFactory()
  2299.   {
  2300.     // creates an observer for http traffic
  2301.     var self = this;
  2302.     var httpObserver = {
  2303.       observeActivity :
  2304.       function HS_SHO_observeActivity(aChannel,
  2305.                                       aActivityType,
  2306.                                       aActivitySubtype,
  2307.                                       aTimestamp,
  2308.                                       aExtraSizeData,
  2309.                                       aExtraStringData)
  2310.       {
  2311.         if (aActivityType ==
  2312.               activityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION ||
  2313.             aActivityType ==
  2314.               activityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
  2315.  
  2316.           aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
  2317.  
  2318.           let transCodes = this.httpTransactionCodes;
  2319.           let hudId;
  2320.  
  2321.           if (aActivitySubtype ==
  2322.               activityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER ) {
  2323.             // Try to get the source window of the request.
  2324.             let win = NetworkHelper.getWindowForRequest(aChannel);
  2325.             if (!win) {
  2326.               return;
  2327.             }
  2328.  
  2329.             // Try to get the hudId that is associated to the window.
  2330.             hudId = self.getHudIdByWindow(win.top);
  2331.             if (!hudId) {
  2332.               return;
  2333.             }
  2334.  
  2335.             // The httpActivity object will hold all information concerning
  2336.             // this request and later response.
  2337.  
  2338.             let httpActivity = {
  2339.               id: self.sequenceId(),
  2340.               hudId: hudId,
  2341.               url: aChannel.URI.spec,
  2342.               method: aChannel.requestMethod,
  2343.               channel: aChannel,
  2344.               charset: win.document.characterSet,
  2345.  
  2346.               panels: [],
  2347.               request: {
  2348.                 header: { }
  2349.               },
  2350.               response: {
  2351.                 header: null
  2352.               },
  2353.               timing: {
  2354.                 "REQUEST_HEADER": aTimestamp
  2355.               }
  2356.             };
  2357.  
  2358.             // Add a new output entry.
  2359.             let loggedNode = self.logNetActivity(httpActivity);
  2360.  
  2361.             // In some cases loggedNode can be undefined (e.g. if an image was
  2362.             // requested). Don't continue in such a case.
  2363.             if (!loggedNode) {
  2364.               return;
  2365.             }
  2366.  
  2367.             aChannel.QueryInterface(Ci.nsITraceableChannel);
  2368.  
  2369.             // The response will be written into the outputStream of this pipe.
  2370.             // This allows us to buffer the data we are receiving and read it
  2371.             // asynchronously.
  2372.             // Both ends of the pipe must be blocking.
  2373.             let sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
  2374.  
  2375.             // The streams need to be blocking because this is required by the
  2376.             // stream tee.
  2377.             sink.init(false, false, HUDService.responsePipeSegmentSize,
  2378.                       PR_UINT32_MAX, null);
  2379.  
  2380.             // Add listener for the response body.
  2381.             let newListener = new ResponseListener(httpActivity);
  2382.  
  2383.             // Remember the input stream, so it isn't released by GC.
  2384.             newListener.inputStream = sink.inputStream;
  2385.             newListener.sink = sink;
  2386.  
  2387.             let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
  2388.                       createInstance(Ci.nsIStreamListenerTee);
  2389.  
  2390.             let originalListener = aChannel.setNewListener(tee);
  2391.  
  2392.             tee.init(originalListener, sink.outputStream, newListener);
  2393.  
  2394.             // Copy the request header data.
  2395.             aChannel.visitRequestHeaders({
  2396.               visitHeader: function(aName, aValue) {
  2397.                 httpActivity.request.header[aName] = aValue;
  2398.               }
  2399.             });
  2400.  
  2401.             // Store the loggedNode and the httpActivity object for later reuse.
  2402.             let linkNode = loggedNode.querySelector(".webconsole-msg-link");
  2403.  
  2404.             httpActivity.messageObject = {
  2405.               messageNode: loggedNode,
  2406.               linkNode:    linkNode
  2407.             };
  2408.             self.openRequests[httpActivity.id] = httpActivity;
  2409.  
  2410.             // Make the network span clickable.
  2411.             linkNode.setAttribute("aria-haspopup", "true");
  2412.             linkNode.addEventListener("mousedown", function(aEvent) {
  2413.               this._startX = aEvent.clientX;
  2414.               this._startY = aEvent.clientY;
  2415.             }, false);
  2416.  
  2417.             linkNode.addEventListener("click", function(aEvent) {
  2418.               if (aEvent.detail != 1 || aEvent.button != 0 ||
  2419.                   (this._startX != aEvent.clientX &&
  2420.                    this._startY != aEvent.clientY)) {
  2421.                 return;
  2422.               }
  2423.  
  2424.               if (!this._panelOpen) {
  2425.                 self.openNetworkPanel(this, httpActivity);
  2426.                 this._panelOpen = true;
  2427.               }
  2428.             }, false);
  2429.           }
  2430.           else {
  2431.             // Iterate over all currently ongoing requests. If aChannel can't
  2432.             // be found within them, then exit this function.
  2433.             let httpActivity = null;
  2434.             for each (let item in self.openRequests) {
  2435.               if (item.channel !== aChannel) {
  2436.                 continue;
  2437.               }
  2438.               httpActivity = item;
  2439.               break;
  2440.             }
  2441.  
  2442.             if (!httpActivity) {
  2443.               return;
  2444.             }
  2445.  
  2446.             hudId = httpActivity.hudId;
  2447.             let msgObject = httpActivity.messageObject;
  2448.  
  2449.             let updatePanel = false;
  2450.             let data;
  2451.             // Store the time information for this activity subtype.
  2452.             httpActivity.timing[transCodes[aActivitySubtype]] = aTimestamp;
  2453.  
  2454.             switch (aActivitySubtype) {
  2455.               case activityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT: {
  2456.                 if (!self.saveRequestAndResponseBodies) {
  2457.                   httpActivity.request.bodyDiscarded = true;
  2458.                   break;
  2459.                 }
  2460.  
  2461.                 let gBrowser = msgObject.messageNode.ownerDocument.
  2462.                                defaultView.gBrowser;
  2463.                 let HUD = HUDService.hudReferences[hudId];
  2464.                 let browser = gBrowser.
  2465.                               getBrowserForDocument(HUD.contentDocument);
  2466.  
  2467.                 let sentBody = NetworkHelper.
  2468.                                readPostTextFromRequest(aChannel, browser);
  2469.                 if (!sentBody) {
  2470.                   // If the request URL is the same as the current page url, then
  2471.                   // we can try to get the posted text from the page directly.
  2472.                   // This check is necessary as otherwise the
  2473.                   //   NetworkHelper.readPostTextFromPage
  2474.                   // function is called for image requests as well but these
  2475.                   // are not web pages and as such don't store the posted text
  2476.                   // in the cache of the webpage.
  2477.                   if (httpActivity.url == browser.contentWindow.location.href) {
  2478.                     sentBody = NetworkHelper.readPostTextFromPage(browser);
  2479.                   }
  2480.                   if (!sentBody) {
  2481.                     sentBody = "";
  2482.                   }
  2483.                 }
  2484.                 httpActivity.request.body = sentBody;
  2485.                 break;
  2486.               }
  2487.  
  2488.               case activityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER: {
  2489.                 // aExtraStringData contains the response header. The first line
  2490.                 // contains the response status (e.g. HTTP/1.1 200 OK).
  2491.                 //
  2492.                 // Note: The response header is not saved here. Calling the
  2493.                 //       aChannel.visitResponseHeaders at this point sometimes
  2494.                 //       causes an NS_ERROR_NOT_AVAILABLE exception. Therefore,
  2495.                 //       the response header and response body is stored on the
  2496.                 //       httpActivity object within the RL_onStopRequest function.
  2497.                 httpActivity.response.status =
  2498.                   aExtraStringData.split(/\r\n|\n|\r/)[0];
  2499.  
  2500.                 // Add the response status.
  2501.                 let linkNode = msgObject.linkNode;
  2502.                 let statusNode = linkNode.
  2503.                   querySelector(".webconsole-msg-status");
  2504.                 let statusText = "[" + httpActivity.response.status + "]";
  2505.                 statusNode.setAttribute("value", statusText);
  2506.  
  2507.                 let clipboardTextPieces =
  2508.                   [ httpActivity.method, httpActivity.url, statusText ];
  2509.                 msgObject.messageNode.clipboardText =
  2510.                   clipboardTextPieces.join(" ");
  2511.  
  2512.                 let status = parseInt(httpActivity.response.status.
  2513.                   replace(/^HTTP\/\d\.\d (\d+).+$/, "$1"));
  2514.  
  2515.                 if (status >= MIN_HTTP_ERROR_CODE &&
  2516.                     status < MAX_HTTP_ERROR_CODE) {
  2517.                   ConsoleUtils.setMessageType(msgObject.messageNode,
  2518.                                               CATEGORY_NETWORK,
  2519.                                               SEVERITY_ERROR);
  2520.                 }
  2521.  
  2522.                 break;
  2523.               }
  2524.  
  2525.               case activityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE: {
  2526.                 let timing = httpActivity.timing;
  2527.                 let requestDuration =
  2528.                   Math.round((timing.RESPONSE_COMPLETE -
  2529.                                 timing.REQUEST_HEADER) / 1000);
  2530.  
  2531.                 // Add the request duration.
  2532.                 let linkNode = msgObject.linkNode;
  2533.                 let statusNode = linkNode.
  2534.                   querySelector(".webconsole-msg-status");
  2535.  
  2536.                 let statusText = httpActivity.response.status;
  2537.                 let timeText = self.getFormatStr("NetworkPanel.durationMS",
  2538.                                                  [ requestDuration ]);
  2539.                 let fullStatusText = "[" + statusText + " " + timeText + "]";
  2540.                 statusNode.setAttribute("value", fullStatusText);
  2541.  
  2542.                 let clipboardTextPieces =
  2543.                   [ httpActivity.method, httpActivity.url, fullStatusText ];
  2544.                 msgObject.messageNode.clipboardText =
  2545.                   clipboardTextPieces.join(" ");
  2546.  
  2547.                 delete httpActivity.messageObject;
  2548.                 delete self.openRequests[httpActivity.id];
  2549.                 updatePanel = true;
  2550.                 break;
  2551.               }
  2552.             }
  2553.  
  2554.             if (updatePanel) {
  2555.               httpActivity.panels.forEach(function(weakRef) {
  2556.                 let panel = weakRef.get();
  2557.                 if (panel) {
  2558.                   panel.update();
  2559.                 }
  2560.               });
  2561.             }
  2562.           }
  2563.         }
  2564.       },
  2565.  
  2566.       httpTransactionCodes: {
  2567.         0x5001: "REQUEST_HEADER",
  2568.         0x5002: "REQUEST_BODY_SENT",
  2569.         0x5003: "RESPONSE_START",
  2570.         0x5004: "RESPONSE_HEADER",
  2571.         0x5005: "RESPONSE_COMPLETE",
  2572.         0x5006: "TRANSACTION_CLOSE",
  2573.  
  2574.         0x804b0003: "STATUS_RESOLVING",
  2575.         0x804b000b: "STATUS_RESOLVED",
  2576.         0x804b0007: "STATUS_CONNECTING_TO",
  2577.         0x804b0004: "STATUS_CONNECTED_TO",
  2578.         0x804b0005: "STATUS_SENDING_TO",
  2579.         0x804b000a: "STATUS_WAITING_FOR",
  2580.         0x804b0006: "STATUS_RECEIVING_FROM"
  2581.       }
  2582.     };
  2583.  
  2584.     this.httpObserver = httpObserver;
  2585.  
  2586.     activityDistributor.addObserver(httpObserver);
  2587.  
  2588.     // This is used to find the correct HTTP response headers.
  2589.     Services.obs.addObserver(this.httpResponseExaminer,
  2590.                              "http-on-examine-response", false);
  2591.   },
  2592.  
  2593.   /**
  2594.    * Observe notifications for the http-on-examine-response topic, coming from
  2595.    * the nsIObserver service.
  2596.    *
  2597.    * @param string aTopic
  2598.    * @param nsIHttpChannel aSubject
  2599.    * @returns void
  2600.    */
  2601.   httpResponseExaminer: function HS_httpResponseExaminer(aSubject, aTopic)
  2602.   {
  2603.     if (aTopic != "http-on-examine-response" ||
  2604.         !(aSubject instanceof Ci.nsIHttpChannel)) {
  2605.       return;
  2606.     }
  2607.  
  2608.     let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
  2609.     let win = NetworkHelper.getWindowForRequest(channel);
  2610.     if (!win) {
  2611.       return;
  2612.     }
  2613.     let hudId = HUDService.getHudIdByWindow(win);
  2614.     if (!hudId) {
  2615.       return;
  2616.     }
  2617.  
  2618.     let response = {
  2619.       id: HUDService.sequenceId(),
  2620.       hudId: hudId,
  2621.       channel: channel,
  2622.       headers: {},
  2623.     };
  2624.  
  2625.     try {
  2626.       channel.visitResponseHeaders({
  2627.         visitHeader: function(aName, aValue) {
  2628.           response.headers[aName] = aValue;
  2629.         }
  2630.       });
  2631.     }
  2632.     catch (ex) {
  2633.       delete response.headers;
  2634.     }
  2635.  
  2636.     if (response.headers) {
  2637.       HUDService.openResponseHeaders[response.id] = response;
  2638.     }
  2639.   },
  2640.  
  2641.   /**
  2642.    * Logs network activity.
  2643.    *
  2644.    * @param object aActivityObject
  2645.    *        The activity to log.
  2646.    * @returns void
  2647.    */
  2648.   logNetActivity: function HS_logNetActivity(aActivityObject)
  2649.   {
  2650.     let hudId = aActivityObject.hudId;
  2651.     let outputNode = this.hudReferences[hudId].outputNode;
  2652.  
  2653.     let chromeDocument = outputNode.ownerDocument;
  2654.     let msgNode = chromeDocument.createElementNS(XUL_NS, "hbox");
  2655.  
  2656.     let methodNode = chromeDocument.createElementNS(XUL_NS, "label");
  2657.     methodNode.setAttribute("value", aActivityObject.method);
  2658.     methodNode.classList.add("webconsole-msg-body-piece");
  2659.     msgNode.appendChild(methodNode);
  2660.  
  2661.     let linkNode = chromeDocument.createElementNS(XUL_NS, "hbox");
  2662.     linkNode.setAttribute("flex", "1");
  2663.     linkNode.classList.add("webconsole-msg-body-piece");
  2664.     linkNode.classList.add("webconsole-msg-link");
  2665.     msgNode.appendChild(linkNode);
  2666.  
  2667.     let urlNode = chromeDocument.createElementNS(XUL_NS, "label");
  2668.     urlNode.setAttribute("crop", "center");
  2669.     urlNode.setAttribute("flex", "1");
  2670.     urlNode.setAttribute("title", aActivityObject.url);
  2671.     urlNode.setAttribute("value", aActivityObject.url);
  2672.     urlNode.classList.add("hud-clickable");
  2673.     urlNode.classList.add("webconsole-msg-body-piece");
  2674.     urlNode.classList.add("webconsole-msg-url");
  2675.     linkNode.appendChild(urlNode);
  2676.  
  2677.     let statusNode = chromeDocument.createElementNS(XUL_NS, "label");
  2678.     statusNode.setAttribute("value", "");
  2679.     statusNode.classList.add("hud-clickable");
  2680.     statusNode.classList.add("webconsole-msg-body-piece");
  2681.     statusNode.classList.add("webconsole-msg-status");
  2682.     linkNode.appendChild(statusNode);
  2683.  
  2684.     let clipboardText = aActivityObject.method + " " + aActivityObject.url;
  2685.  
  2686.     let messageNode = ConsoleUtils.createMessageNode(chromeDocument,
  2687.                                                      CATEGORY_NETWORK,
  2688.                                                      SEVERITY_LOG,
  2689.                                                      msgNode,
  2690.                                                      hudId,
  2691.                                                      null,
  2692.                                                      null,
  2693.                                                      clipboardText);
  2694.  
  2695.     ConsoleUtils.outputMessageNode(messageNode, aActivityObject.hudId);
  2696.     return messageNode;
  2697.   },
  2698.  
  2699.   /**
  2700.    * Initialize the JSTerm object to create a JS Workspace by attaching the UI
  2701.    * into the given parent node, using the mixin.
  2702.    *
  2703.    * @param nsIDOMWindow aContext the context used for evaluating user input
  2704.    * @param nsIDOMNode aParentNode where to attach the JSTerm
  2705.    * @param object aConsole
  2706.    *        Console object used within the JSTerm instance to report errors
  2707.    *        and log data (by calling console.error(), console.log(), etc).
  2708.    */
  2709.   initializeJSTerm: function HS_initializeJSTerm(aContext, aParentNode, aConsole)
  2710.   {
  2711.     // create Initial JS Workspace:
  2712.     var context = Cu.getWeakReference(aContext);
  2713.  
  2714.     // Attach the UI into the target parent node using the mixin.
  2715.     var firefoxMixin = new JSTermFirefoxMixin(context, aParentNode);
  2716.     var jsTerm = new JSTerm(context, aParentNode, firefoxMixin, aConsole);
  2717.  
  2718.     // TODO: injection of additional functionality needs re-thinking/api
  2719.     // see bug 559748
  2720.   },
  2721.  
  2722.   /**
  2723.    * Creates a generator that always returns a unique number for use in the
  2724.    * indexes
  2725.    *
  2726.    * @returns Generator
  2727.    */
  2728.   createSequencer: function HS_createSequencer(aInt)
  2729.   {
  2730.     function sequencer(aInt)
  2731.     {
  2732.       while(1) {
  2733.         aInt++;
  2734.         yield aInt;
  2735.       }
  2736.     }
  2737.     return sequencer(aInt);
  2738.   },
  2739.  
  2740.   // See jsapi.h (JSErrorReport flags):
  2741.   // http://mxr.mozilla.org/mozilla-central/source/js/src/jsapi.h#3429
  2742.   scriptErrorFlags: {
  2743.     0: "error", // JSREPORT_ERROR
  2744.     1: "warn", // JSREPORT_WARNING
  2745.     2: "exception", // JSREPORT_EXCEPTION
  2746.     4: "error", // JSREPORT_STRICT | JSREPORT_ERROR
  2747.     5: "warn", // JSREPORT_STRICT | JSREPORT_WARNING
  2748.     8: "error", // JSREPORT_STRICT_MODE_ERROR
  2749.     13: "warn", // JSREPORT_STRICT_MODE_ERROR | JSREPORT_WARNING | JSREPORT_ERROR
  2750.   },
  2751.  
  2752.   /**
  2753.    * replacement strings (L10N)
  2754.    */
  2755.   scriptMsgLogLevel: {
  2756.     0: "typeError", // JSREPORT_ERROR
  2757.     1: "typeWarning", // JSREPORT_WARNING
  2758.     2: "typeException", // JSREPORT_EXCEPTION
  2759.     4: "typeError", // JSREPORT_STRICT | JSREPORT_ERROR
  2760.     5: "typeStrict", // JSREPORT_STRICT | JSREPORT_WARNING
  2761.     8: "typeError", // JSREPORT_STRICT_MODE_ERROR
  2762.     13: "typeWarning", // JSREPORT_STRICT_MODE_ERROR | JSREPORT_WARNING | JSREPORT_ERROR
  2763.   },
  2764.  
  2765.   /**
  2766.    * onTabClose event handler function
  2767.    *
  2768.    * @param aEvent
  2769.    * @returns void
  2770.    */
  2771.   onTabClose: function HS_onTabClose(aEvent)
  2772.   {
  2773.     this.deactivateHUDForContext(aEvent.target, false);
  2774.   },
  2775.  
  2776.   /**
  2777.    * Called whenever a browser window closes. Cleans up any consoles still
  2778.    * around.
  2779.    *
  2780.    * @param nsIDOMEvent aEvent
  2781.    *        The dispatched event.
  2782.    * @returns void
  2783.    */
  2784.   onWindowUnload: function HS_onWindowUnload(aEvent)
  2785.   {
  2786.     let window = aEvent.target.defaultView;
  2787.  
  2788.     window.removeEventListener("unload", this.onWindowUnload, false);
  2789.  
  2790.     let gBrowser = window.gBrowser;
  2791.     let tabContainer = gBrowser.tabContainer;
  2792.  
  2793.     tabContainer.removeEventListener("TabClose", this.onTabClose, false);
  2794.  
  2795.     let tab = tabContainer.firstChild;
  2796.     while (tab != null) {
  2797.       this.deactivateHUDForContext(tab, false);
  2798.       tab = tab.nextSibling;
  2799.     }
  2800.  
  2801.     if (window.webConsoleCommandController) {
  2802.       window.controllers.removeController(window.webConsoleCommandController);
  2803.       window.webConsoleCommandController = null;
  2804.     }
  2805.   },
  2806.  
  2807.   /**
  2808.    * windowInitializer - checks what Gecko app is running and inits the HUD
  2809.    *
  2810.    * @param nsIDOMWindow aContentWindow
  2811.    * @returns void
  2812.    */
  2813.   windowInitializer: function HS_WindowInitalizer(aContentWindow)
  2814.   {
  2815.     var xulWindow = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  2816.       .getInterface(Ci.nsIWebNavigation)
  2817.                       .QueryInterface(Ci.nsIDocShell)
  2818.                       .chromeEventHandler.ownerDocument.defaultView;
  2819.  
  2820.     let xulWindow = unwrap(xulWindow);
  2821.  
  2822.     let docElem = xulWindow.document.documentElement;
  2823.     if (!docElem || docElem.getAttribute("windowtype") != "navigator:browser" ||
  2824.         !xulWindow.gBrowser) {
  2825.       // Do not do anything unless we have a browser window.
  2826.       // This may be a view-source window or other type of non-browser window.
  2827.       return;
  2828.     }
  2829.  
  2830.     let gBrowser = xulWindow.gBrowser;
  2831.  
  2832.     let _browser = gBrowser.
  2833.       getBrowserForDocument(aContentWindow.top.document);
  2834.  
  2835.     // ignore newly created documents that don't belong to a tab's browser
  2836.     if (!_browser) {
  2837.       return;
  2838.     }
  2839.  
  2840.     let nBox = gBrowser.getNotificationBox(_browser);
  2841.     let nBoxId = nBox.getAttribute("id");
  2842.     let hudId = "hud_" + nBoxId;
  2843.     let windowUI = nBox.ownerDocument.getElementById("console_window_" + hudId);
  2844.     if (windowUI) {
  2845.       // The Web Console popup is already open, no need to continue.
  2846.       if (aContentWindow == aContentWindow.top) {
  2847.         let hud = this.hudReferences[hudId];
  2848.         hud.reattachConsole(aContentWindow);
  2849.       }
  2850.       return;
  2851.     }
  2852.  
  2853.     if (!this.canActivateContext(hudId)) {
  2854.       return;
  2855.     }
  2856.  
  2857.     xulWindow.addEventListener("unload", this.onWindowUnload, false);
  2858.     gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose, false);
  2859.  
  2860.     this.registerDisplay(hudId);
  2861.  
  2862.     let hudNode;
  2863.     let childNodes = nBox.childNodes;
  2864.  
  2865.     for (let i = 0; i < childNodes.length; i++) {
  2866.       let id = childNodes[i].getAttribute("id");
  2867.       // `id` is a string with the format "hud_<number>".
  2868.       if (id.split("_")[0] == "hud") {
  2869.         hudNode = childNodes[i];
  2870.         break;
  2871.       }
  2872.     }
  2873.  
  2874.     let hud;
  2875.     // If there is no HUD for this tab create a new one.
  2876.     if (!hudNode) {
  2877.       // get nBox object and call new HUD
  2878.       let config = { parentNode: nBox,
  2879.                      contentWindow: aContentWindow
  2880.                    };
  2881.  
  2882.       hud = new HeadsUpDisplay(config);
  2883.  
  2884.       HUDService.registerHUDReference(hud);
  2885.       let windowId = this.getWindowId(aContentWindow.top);
  2886.       this.windowIds[windowId] = hudId;
  2887.  
  2888.       hud.progressListener = new ConsoleProgressListener(hudId);
  2889.  
  2890.       _browser.webProgress.addProgressListener(hud.progressListener,
  2891.         Ci.nsIWebProgress.NOTIFY_STATE_ALL);
  2892.     }
  2893.     else {
  2894.       hud = this.hudReferences[hudId];
  2895.       if (aContentWindow == aContentWindow.top) {
  2896.         // TODO: name change?? doesn't actually re-attach the console
  2897.         hud.reattachConsole(aContentWindow);
  2898.       }
  2899.     }
  2900.  
  2901.     // Need to detect that the console component has been paved over.
  2902.     let consoleObject = unwrap(aContentWindow).console;
  2903.     if (!("__mozillaConsole__" in consoleObject))
  2904.       this.logWarningAboutReplacedAPI(hudId);
  2905.  
  2906.     // register the controller to handle "select all" properly
  2907.     this.createController(xulWindow);
  2908.   },
  2909.  
  2910.   /**
  2911.    * Adds the command controller to the XUL window if it's not already present.
  2912.    *
  2913.    * @param nsIDOMWindow aWindow
  2914.    *        The browser XUL window.
  2915.    * @returns void
  2916.    */
  2917.   createController: function HUD_createController(aWindow)
  2918.   {
  2919.     if (aWindow.webConsoleCommandController == null) {
  2920.       aWindow.webConsoleCommandController = new CommandController(aWindow);
  2921.       aWindow.controllers.insertControllerAt(0,
  2922.         aWindow.webConsoleCommandController);
  2923.     }
  2924.   },
  2925.  
  2926.   /**
  2927.    * Animates the Console appropriately.
  2928.    *
  2929.    * @param string aHUDId The ID of the console.
  2930.    * @param string aDirection Whether to animate the console appearing
  2931.    *        (ANIMATE_IN) or disappearing (ANIMATE_OUT).
  2932.    * @param function aCallback An optional callback, which will be called with
  2933.    *        the "transitionend" event passed as a parameter once the animation
  2934.    *        finishes.
  2935.    */
  2936.   animate: function HS_animate(aHUDId, aDirection, aCallback)
  2937.   {
  2938.     let hudBox = this.getHudReferenceById(aHUDId).HUDBox;
  2939.     if (!hudBox.classList.contains("animated")) {
  2940.       if (aCallback) {
  2941.         aCallback();
  2942.       }
  2943.       return;
  2944.     }
  2945.  
  2946.     switch (aDirection) {
  2947.       case ANIMATE_OUT:
  2948.         hudBox.style.height = 0;
  2949.         break;
  2950.       case ANIMATE_IN:
  2951.         this.resetHeight(aHUDId);
  2952.         break;
  2953.     }
  2954.  
  2955.     if (aCallback) {
  2956.       hudBox.addEventListener("transitionend", aCallback, false);
  2957.     }
  2958.   },
  2959.  
  2960.   /**
  2961.    * Disables all animation for a console, for unit testing. After this call,
  2962.    * the console will instantly take on a reasonable height, and the close
  2963.    * animation will not occur.
  2964.    *
  2965.    * @param string aHUDId The ID of the console.
  2966.    */
  2967.   disableAnimation: function HS_disableAnimation(aHUDId)
  2968.   {
  2969.     let hudBox = HUDService.hudReferences[aHUDId].HUDBox;
  2970.     if (hudBox.classList.contains("animated")) {
  2971.       hudBox.classList.remove("animated");
  2972.       this.resetHeight(aHUDId);
  2973.     }
  2974.   },
  2975.  
  2976.   /**
  2977.    * Reset the height of the Web Console.
  2978.    *
  2979.    * @param string aHUDId The ID of the Web Console.
  2980.    */
  2981.   resetHeight: function HS_resetHeight(aHUDId)
  2982.   {
  2983.     let HUD = this.hudReferences[aHUDId];
  2984.     let innerHeight = HUD.contentWindow.innerHeight;
  2985.     let chromeWindow = HUD.chromeDocument.defaultView;
  2986.     if (!HUD.consolePanel) {
  2987.       let splitterStyle = chromeWindow.getComputedStyle(HUD.splitter, null);
  2988.       innerHeight += parseInt(splitterStyle.height) +
  2989.                      parseInt(splitterStyle.borderTopWidth) +
  2990.                      parseInt(splitterStyle.borderBottomWidth);
  2991.     }
  2992.  
  2993.     let boxStyle = chromeWindow.getComputedStyle(HUD.HUDBox, null);
  2994.     innerHeight += parseInt(boxStyle.height) +
  2995.                    parseInt(boxStyle.borderTopWidth) +
  2996.                    parseInt(boxStyle.borderBottomWidth);
  2997.  
  2998.     let height = this.lastConsoleHeight > 0 ? this.lastConsoleHeight :
  2999.       Math.ceil(innerHeight * DEFAULT_CONSOLE_HEIGHT);
  3000.  
  3001.     if ((innerHeight - height) < MINIMUM_PAGE_HEIGHT) {
  3002.       height = innerHeight - MINIMUM_PAGE_HEIGHT;
  3003.     }
  3004.     else if (height < MINIMUM_CONSOLE_HEIGHT) {
  3005.       height = MINIMUM_CONSOLE_HEIGHT;
  3006.     }
  3007.  
  3008.     HUD.HUDBox.style.height = height + "px";
  3009.   },
  3010.  
  3011.   /**
  3012.    * Remember the height of the given Web Console, such that it can later be
  3013.    * reused when other Web Consoles are open.
  3014.    *
  3015.    * @param string aHUDId The ID of the Web Console.
  3016.    */
  3017.   storeHeight: function HS_storeHeight(aHUDId)
  3018.   {
  3019.     let hudBox = this.hudReferences[aHUDId].HUDBox;
  3020.     let window = hudBox.ownerDocument.defaultView;
  3021.     let style = window.getComputedStyle(hudBox, null);
  3022.     let height = parseInt(style.height);
  3023.     height += parseInt(style.borderTopWidth);
  3024.     height += parseInt(style.borderBottomWidth);
  3025.     this.lastConsoleHeight = height;
  3026.  
  3027.     let pref = Services.prefs.getIntPref("devtools.hud.height");
  3028.     if (pref > -1) {
  3029.       Services.prefs.setIntPref("devtools.hud.height", height);
  3030.     }
  3031.   },
  3032.  
  3033.   /**
  3034.    * Copies the selected items to the system clipboard.
  3035.    *
  3036.    * @param nsIDOMNode aOutputNode
  3037.    *        The output node.
  3038.    * @returns void
  3039.    */
  3040.   copySelectedItems: function HS_copySelectedItems(aOutputNode)
  3041.   {
  3042.     // Gather up the selected items and concatenate their clipboard text.
  3043.  
  3044.     let strings = [];
  3045.     let newGroup = false;
  3046.  
  3047.     let children = aOutputNode.children;
  3048.  
  3049.     for (let i = 0; i < children.length; i++) {
  3050.       let item = children[i];
  3051.       if (!item.selected) {
  3052.         continue;
  3053.       }
  3054.  
  3055.       // Add dashes between groups so that group boundaries show up in the
  3056.       // copied output.
  3057.       if (i > 0 && item.classList.contains("webconsole-new-group")) {
  3058.         newGroup = true;
  3059.       }
  3060.  
  3061.       // Ensure the selected item hasn't been filtered by type or string.
  3062.       if (!item.classList.contains("hud-filtered-by-type") &&
  3063.           !item.classList.contains("hud-filtered-by-string")) {
  3064.         let timestampString = ConsoleUtils.timestampString(item.timestamp);
  3065.         if (newGroup) {
  3066.           strings.push("--");
  3067.           newGroup = false;
  3068.         }
  3069.         strings.push("[" + timestampString + "] " + item.clipboardText);
  3070.       }
  3071.     }
  3072.     clipboardHelper.copyString(strings.join("\n"));
  3073.   }
  3074. };
  3075.  
  3076. //////////////////////////////////////////////////////////////////////////
  3077. // HeadsUpDisplay
  3078. //////////////////////////////////////////////////////////////////////////
  3079.  
  3080. /*
  3081.  * HeadsUpDisplay is an interactive console initialized *per tab*  that
  3082.  * displays console log data as well as provides an interactive terminal to
  3083.  * manipulate the current tab's document content.
  3084.  * */
  3085. function HeadsUpDisplay(aConfig)
  3086. {
  3087.   // sample config: { parentNode: aDOMNode,
  3088.   //                  // or
  3089.   //                  parentNodeId: "myHUDParent123",
  3090.   //
  3091.   //                  placement: "appendChild"
  3092.   //                  // or
  3093.   //                  placement: "insertBefore",
  3094.   //                  placementChildNodeIndex: 0,
  3095.   //                }
  3096.  
  3097.   this.HUDBox = null;
  3098.  
  3099.   if (aConfig.parentNode) {
  3100.     // TODO: need to replace these DOM calls with internal functions
  3101.     // that operate on each application's node structure
  3102.     // better yet, we keep these functions in a "bridgeModule" or the HUDService
  3103.     // to keep a registry of nodeGetters for each application
  3104.     // see bug 568647
  3105.     this.parentNode = aConfig.parentNode;
  3106.     this.notificationBox = aConfig.parentNode;
  3107.     this.chromeDocument = aConfig.parentNode.ownerDocument;
  3108.     this.contentWindow = aConfig.contentWindow;
  3109.     this.uriSpec = aConfig.contentWindow.location.href;
  3110.     this.hudId = "hud_" + aConfig.parentNode.getAttribute("id");
  3111.   }
  3112.   else {
  3113.     // parentNodeId is the node's id where we attach the HUD
  3114.     // TODO: is the "navigator:browser" below used in all Gecko Apps?
  3115.     // see bug 568647
  3116.     let windowEnum = Services.wm.getEnumerator("navigator:browser");
  3117.     let parentNode;
  3118.     let contentDocument;
  3119.     let contentWindow;
  3120.     let chromeDocument;
  3121.  
  3122.     // TODO: the following  part is still very Firefox specific
  3123.     // see bug 568647
  3124.  
  3125.     while (windowEnum.hasMoreElements()) {
  3126.       let window = windowEnum.getNext();
  3127.       try {
  3128.         let gBrowser = window.gBrowser;
  3129.         let _browsers = gBrowser.browsers;
  3130.         let browserLen = _browsers.length;
  3131.  
  3132.         for (var i = 0; i < browserLen; i++) {
  3133.           var _notificationBox = gBrowser.getNotificationBox(_browsers[i]);
  3134.           this.notificationBox = _notificationBox;
  3135.  
  3136.           if (_notificationBox.getAttribute("id") == aConfig.parentNodeId) {
  3137.             this.parentNodeId = _notificationBox.getAttribute("id");
  3138.             this.hudId = "hud_" + this.parentNodeId;
  3139.  
  3140.             parentNode = _notificationBox;
  3141.  
  3142.             this.contentDocument =
  3143.               _notificationBox.childNodes[0].contentDocument;
  3144.             this.contentWindow =
  3145.               _notificationBox.childNodes[0].contentWindow;
  3146.             this.uriSpec = aConfig.contentWindow.location.href;
  3147.  
  3148.             this.chromeDocument =
  3149.               _notificationBox.ownerDocument;
  3150.  
  3151.             break;
  3152.           }
  3153.         }
  3154.       }
  3155.       catch (ex) {
  3156.         Cu.reportError(ex);
  3157.       }
  3158.  
  3159.       if (parentNode) {
  3160.         break;
  3161.       }
  3162.     }
  3163.     if (!parentNode) {
  3164.       throw new Error(this.ERRORS.PARENTNODE_NOT_FOUND);
  3165.     }
  3166.     this.parentNode = parentNode;
  3167.     this.notificationBox = parentNode;
  3168.   }
  3169.  
  3170.   // create textNode Factory:
  3171.   this.textFactory = NodeFactory("text", "xul", this.chromeDocument);
  3172.  
  3173.   this.chromeWindow = this.chromeDocument.defaultView;
  3174.  
  3175.   // create a panel dynamically and attach to the parentNode
  3176.   this.createHUD();
  3177.  
  3178.   this.HUDBox.lastTimestamp = 0;
  3179.   // create the JSTerm input element
  3180.   try {
  3181.     this.createConsoleInput(this.contentWindow, this.consoleWrap, this.outputNode);
  3182.     if (this.jsterm) {
  3183.       this.jsterm.inputNode.focus();
  3184.     }
  3185.     if (this.gcliterm) {
  3186.       this.gcliterm.inputNode.focus();
  3187.     }
  3188.   }
  3189.   catch (ex) {
  3190.     Cu.reportError(ex);
  3191.   }
  3192.  
  3193.   // A cache for tracking repeated CSS Nodes.
  3194.   this.cssNodes = {};
  3195. }
  3196.  
  3197. HeadsUpDisplay.prototype = {
  3198.  
  3199.   consolePanel: null,
  3200.  
  3201.   /**
  3202.    * The nesting depth of the currently active console group.
  3203.    */
  3204.   groupDepth: 0,
  3205.  
  3206.   get mainPopupSet()
  3207.   {
  3208.     return this.chromeDocument.getElementById("mainPopupSet");
  3209.   },
  3210.  
  3211.   /**
  3212.    * Get the tab associated to the HeadsUpDisplay object.
  3213.    */
  3214.   get tab()
  3215.   {
  3216.     // TODO: we should only keep a reference to the tab object and use
  3217.     // getters to determine the rest of objects we need - the chrome window,
  3218.     // document, etc. We should simplify the entire code to use only a single
  3219.     // tab object ref. See bug 656231.
  3220.     let tab = null;
  3221.     let id = this.notificationBox.id;
  3222.     Array.some(this.chromeDocument.defaultView.gBrowser.tabs, function(aTab) {
  3223.       if (aTab.linkedPanel == id) {
  3224.         tab = aTab;
  3225.         return true;
  3226.       }
  3227.     });
  3228.  
  3229.     return tab;
  3230.   },
  3231.  
  3232.   /**
  3233.    * Create a panel to open the web console if it should float above
  3234.    * the content in its own window.
  3235.    */
  3236.   createOwnWindowPanel: function HUD_createOwnWindowPanel()
  3237.   {
  3238.     if (this.uiInOwnWindow) {
  3239.       return this.consolePanel;
  3240.     }
  3241.  
  3242.     let width = 0;
  3243.     try {
  3244.       width = Services.prefs.getIntPref("devtools.webconsole.width");
  3245.     }
  3246.     catch (ex) {}
  3247.  
  3248.     if (width < 1) {
  3249.       width = this.HUDBox.clientWidth || this.contentWindow.innerWidth;
  3250.     }
  3251.  
  3252.     let height = this.HUDBox.clientHeight;
  3253.  
  3254.     let top = 0;
  3255.     try {
  3256.       top = Services.prefs.getIntPref("devtools.webconsole.top");
  3257.     }
  3258.     catch (ex) {}
  3259.  
  3260.     let left = 0;
  3261.     try {
  3262.       left = Services.prefs.getIntPref("devtools.webconsole.left");
  3263.     }
  3264.     catch (ex) {}
  3265.  
  3266.     let panel = this.chromeDocument.createElementNS(XUL_NS, "panel");
  3267.  
  3268.     let config = { id: "console_window_" + this.hudId,
  3269.                    label: this.getPanelTitle(),
  3270.                    titlebar: "normal",
  3271.                    noautohide: "true",
  3272.                    norestorefocus: "true",
  3273.                    close: "true",
  3274.                    flex: "1",
  3275.                    hudId: this.hudId,
  3276.                    width: width,
  3277.                    position: "overlap",
  3278.                    top: top,
  3279.                    left: left,
  3280.                  };
  3281.  
  3282.     for (let attr in config) {
  3283.       panel.setAttribute(attr, config[attr]);
  3284.     }
  3285.  
  3286.     panel.classList.add("web-console-panel");
  3287.  
  3288.     let onPopupShown = (function HUD_onPopupShown() {
  3289.       panel.removeEventListener("popupshown", onPopupShown, false);
  3290.  
  3291.       // Make sure that the HUDBox size updates when the panel is resized.
  3292.  
  3293.       let height = panel.clientHeight;
  3294.  
  3295.       this.HUDBox.style.height = "auto";
  3296.       this.HUDBox.setAttribute("flex", "1");
  3297.  
  3298.       panel.setAttribute("height", height);
  3299.  
  3300.       // Scroll the outputNode back to the last location.
  3301.       if (lastIndex > -1 && lastIndex < this.outputNode.getRowCount()) {
  3302.         this.outputNode.ensureIndexIsVisible(lastIndex);
  3303.       }
  3304.  
  3305.       if (this.jsterm) {
  3306.         this.jsterm.inputNode.focus();
  3307.       }
  3308.       if (this.gcliterm) {
  3309.         this.gcliterm.inputNode.focus();
  3310.       }
  3311.     }).bind(this);
  3312.  
  3313.     panel.addEventListener("popupshown", onPopupShown,false);
  3314.  
  3315.     let onPopupHidden = (function HUD_onPopupHidden(aEvent) {
  3316.       if (aEvent.target != panel) {
  3317.         return;
  3318.       }
  3319.  
  3320.       panel.removeEventListener("popuphidden", onPopupHidden, false);
  3321.  
  3322.       let width = 0;
  3323.       try {
  3324.         width = Services.prefs.getIntPref("devtools.webconsole.width");
  3325.       }
  3326.       catch (ex) { }
  3327.  
  3328.       if (width > 0) {
  3329.         Services.prefs.setIntPref("devtools.webconsole.width", panel.clientWidth);
  3330.       }
  3331.  
  3332.       // Are we destroying the HUD or repositioning it?
  3333.       if (this.consoleWindowUnregisterOnHide) {
  3334.         HUDService.deactivateHUDForContext(this.tab, false);
  3335.       } else {
  3336.         this.consoleWindowUnregisterOnHide = true;
  3337.       }
  3338.     }).bind(this);
  3339.  
  3340.     panel.addEventListener("popuphidden", onPopupHidden, false);
  3341.  
  3342.     let lastIndex = -1;
  3343.  
  3344.     if (this.outputNode.getIndexOfFirstVisibleRow) {
  3345.       lastIndex = this.outputNode.getIndexOfFirstVisibleRow() +
  3346.                   this.outputNode.getNumberOfVisibleRows() - 1;
  3347.     }
  3348.  
  3349.     if (this.splitter.parentNode) {
  3350.       this.splitter.parentNode.removeChild(this.splitter);
  3351.     }
  3352.     panel.appendChild(this.HUDBox);
  3353.  
  3354.     let space = this.chromeDocument.createElement("spacer");
  3355.     space.setAttribute("flex", "1");
  3356.  
  3357.     let bottomBox = this.chromeDocument.createElement("hbox");
  3358.     space.setAttribute("flex", "1");
  3359.  
  3360.     let resizer = this.chromeDocument.createElement("resizer");
  3361.     resizer.setAttribute("dir", "bottomend");
  3362.     resizer.setAttribute("element", config.id);
  3363.  
  3364.     bottomBox.appendChild(space);
  3365.     bottomBox.appendChild(resizer);
  3366.  
  3367.     panel.appendChild(bottomBox);
  3368.  
  3369.     this.mainPopupSet.appendChild(panel);
  3370.  
  3371.     Services.prefs.setCharPref("devtools.webconsole.position", "window");
  3372.  
  3373.     panel.openPopup(null, "overlay", left, top, false, false);
  3374.  
  3375.     this.consolePanel = panel;
  3376.     this.consoleWindowUnregisterOnHide = true;
  3377.  
  3378.     return panel;
  3379.   },
  3380.  
  3381.   /**
  3382.    * Retrieve the Web Console panel title.
  3383.    *
  3384.    * @return string
  3385.    *         The Web Console panel title.
  3386.    */
  3387.   getPanelTitle: function HUD_getPanelTitle()
  3388.   {
  3389.     return this.getFormatStr("webConsoleWindowTitleAndURL", [this.uriSpec]);
  3390.   },
  3391.  
  3392.   positions: {
  3393.     above: 0, // the childNode index
  3394.     below: 2,
  3395.     window: null
  3396.   },
  3397.  
  3398.   consoleWindowUnregisterOnHide: true,
  3399.  
  3400.   /**
  3401.    * Re-position the console
  3402.    */
  3403.   positionConsole: function HUD_positionConsole(aPosition)
  3404.   {
  3405.     if (!(aPosition in this.positions)) {
  3406.       throw new Error("Incorrect argument: " + aPosition +
  3407.         ". Cannot position Web Console");
  3408.     }
  3409.  
  3410.     if (aPosition == "window") {
  3411.       let closeButton = this.consoleFilterToolbar.
  3412.         querySelector(".webconsole-close-button");
  3413.       closeButton.setAttribute("hidden", "true");
  3414.       this.createOwnWindowPanel();
  3415.       this.positionMenuitems.window.setAttribute("checked", true);
  3416.       if (this.positionMenuitems.last) {
  3417.         this.positionMenuitems.last.setAttribute("checked", false);
  3418.       }
  3419.       this.positionMenuitems.last = this.positionMenuitems[aPosition];
  3420.       this.uiInOwnWindow = true;
  3421.       return;
  3422.     }
  3423.  
  3424.     let height = this.HUDBox.clientHeight;
  3425.  
  3426.     // get the node position index
  3427.     let nodeIdx = this.positions[aPosition];
  3428.     let nBox = this.notificationBox;
  3429.     let node = nBox.childNodes[nodeIdx];
  3430.  
  3431.     // check to see if console is already positioned in aPosition
  3432.     if (node == this.HUDBox) {
  3433.       return;
  3434.     }
  3435.  
  3436.     let lastIndex = -1;
  3437.  
  3438.     if (this.outputNode.getIndexOfFirstVisibleRow) {
  3439.       lastIndex = this.outputNode.getIndexOfFirstVisibleRow() +
  3440.                   this.outputNode.getNumberOfVisibleRows() - 1;
  3441.     }
  3442.  
  3443.     // remove the console and splitter and reposition
  3444.     if (this.splitter.parentNode) {
  3445.       this.splitter.parentNode.removeChild(this.splitter);
  3446.     }
  3447.  
  3448.     if (aPosition == "below") {
  3449.       nBox.appendChild(this.splitter);
  3450.       nBox.appendChild(this.HUDBox);
  3451.     }
  3452.     else {
  3453.       nBox.insertBefore(this.splitter, node);
  3454.       nBox.insertBefore(this.HUDBox, this.splitter);
  3455.     }
  3456.  
  3457.     this.positionMenuitems[aPosition].setAttribute("checked", true);
  3458.     if (this.positionMenuitems.last) {
  3459.       this.positionMenuitems.last.setAttribute("checked", false);
  3460.     }
  3461.     this.positionMenuitems.last = this.positionMenuitems[aPosition];
  3462.  
  3463.     Services.prefs.setCharPref("devtools.webconsole.position", aPosition);
  3464.  
  3465.     if (lastIndex > -1 && lastIndex < this.outputNode.getRowCount()) {
  3466.       this.outputNode.ensureIndexIsVisible(lastIndex);
  3467.     }
  3468.  
  3469.     let closeButton = this.consoleFilterToolbar.
  3470.       getElementsByClassName("webconsole-close-button")[0];
  3471.     closeButton.removeAttribute("hidden");
  3472.  
  3473.     this.uiInOwnWindow = false;
  3474.     if (this.consolePanel) {
  3475.       // must destroy the consolePanel
  3476.       this.consoleWindowUnregisterOnHide = false;
  3477.       this.consolePanel.hidePopup();
  3478.       this.consolePanel.parentNode.removeChild(this.consolePanel);
  3479.       this.consolePanel = null;   // remove this as we're not in panel anymore
  3480.       this.HUDBox.removeAttribute("flex");
  3481.       this.HUDBox.removeAttribute("height");
  3482.       this.HUDBox.style.height = height + "px";
  3483.     }
  3484.  
  3485.     if (this.jsterm) {
  3486.       this.jsterm.inputNode.focus();
  3487.     }
  3488.     if (this.gcliterm) {
  3489.       this.gcliterm.inputNode.focus();
  3490.     }
  3491.   },
  3492.  
  3493.   /**
  3494.    * L10N shortcut function
  3495.    *
  3496.    * @param string aName
  3497.    * @returns string
  3498.    */
  3499.   getStr: function HUD_getStr(aName)
  3500.   {
  3501.     return stringBundle.GetStringFromName(aName);
  3502.   },
  3503.  
  3504.   /**
  3505.    * L10N shortcut function
  3506.    *
  3507.    * @param string aName
  3508.    * @param array aArray
  3509.    * @returns string
  3510.    */
  3511.   getFormatStr: function HUD_getFormatStr(aName, aArray)
  3512.   {
  3513.     return stringBundle.formatStringFromName(aName, aArray, aArray.length);
  3514.   },
  3515.  
  3516.   /**
  3517.    * The JSTerm object that contains the console's inputNode
  3518.    *
  3519.    */
  3520.   jsterm: null,
  3521.  
  3522.   /**
  3523.    * The GcliTerm object that contains the console's GCLI
  3524.    */
  3525.   gcliterm: null,
  3526.  
  3527.   /**
  3528.    * creates and attaches the console input node
  3529.    *
  3530.    * @param nsIDOMWindow aWindow
  3531.    * @returns void
  3532.    */
  3533.   createConsoleInput:
  3534.   function HUD_createConsoleInput(aWindow, aParentNode, aExistingConsole)
  3535.   {
  3536.     let usegcli = false;
  3537.     try {
  3538.       usegcli = Services.prefs.getBoolPref("devtools.gcli.enable");
  3539.     }
  3540.     catch (ex) {}
  3541.  
  3542.     if (appName() == "FIREFOX") {
  3543.       if (!usegcli) {
  3544.         let context = Cu.getWeakReference(aWindow);
  3545.         let mixin = new JSTermFirefoxMixin(context, aParentNode,
  3546.                                            aExistingConsole);
  3547.         this.jsterm = new JSTerm(context, aParentNode, mixin, this.console);
  3548.       }
  3549.       else {
  3550.         this.gcliterm = new GcliTerm(aWindow, this.hudId, this.chromeDocument,
  3551.                                      this.console, this.hintNode);
  3552.         aParentNode.appendChild(this.gcliterm.element);
  3553.       }
  3554.     }
  3555.     else {
  3556.       throw new Error("Unsupported Gecko Application");
  3557.     }
  3558.   },
  3559.  
  3560.   /**
  3561.    * Re-attaches a console when the contentWindow is recreated
  3562.    *
  3563.    * @param nsIDOMWindow aContentWindow
  3564.    * @returns void
  3565.    */
  3566.   reattachConsole: function HUD_reattachConsole(aContentWindow)
  3567.   {
  3568.     this.contentWindow = aContentWindow;
  3569.     this.contentDocument = this.contentWindow.document;
  3570.     this.uriSpec = this.contentWindow.location.href;
  3571.  
  3572.     if (this.consolePanel) {
  3573.       this.consolePanel.label = this.getPanelTitle();
  3574.     }
  3575.  
  3576.     if (this.jsterm) {
  3577.       this.jsterm.context = Cu.getWeakReference(this.contentWindow);
  3578.       this.jsterm.console = this.console;
  3579.       this.jsterm.createSandbox();
  3580.     }
  3581.     else if (this.gcliterm) {
  3582.       this.gcliterm.reattachConsole(this.contentWindow, this.console);
  3583.     }
  3584.     else {
  3585.       this.createConsoleInput(this.contentWindow, this.consoleWrap, this.outputNode);
  3586.     }
  3587.   },
  3588.  
  3589.   /**
  3590.    * Shortcut to make XUL nodes
  3591.    *
  3592.    * @param string aTag
  3593.    * @returns nsIDOMNode
  3594.    */
  3595.   makeXULNode:
  3596.   function HUD_makeXULNode(aTag)
  3597.   {
  3598.     return this.chromeDocument.createElement(aTag);
  3599.   },
  3600.  
  3601.   /**
  3602.    * Build the UI of each HeadsUpDisplay
  3603.    *
  3604.    * @returns nsIDOMNode
  3605.    */
  3606.   makeHUDNodes: function HUD_makeHUDNodes()
  3607.   {
  3608.     let self = this;
  3609.  
  3610.     this.splitter = this.makeXULNode("splitter");
  3611.     this.splitter.setAttribute("class", "hud-splitter");
  3612.  
  3613.     this.HUDBox = this.makeXULNode("vbox");
  3614.     this.HUDBox.setAttribute("id", this.hudId);
  3615.     this.HUDBox.setAttribute("class", "hud-box animated");
  3616.     this.HUDBox.style.height = 0;
  3617.  
  3618.     let outerWrap = this.makeXULNode("vbox");
  3619.     outerWrap.setAttribute("class", "hud-outer-wrapper");
  3620.     outerWrap.setAttribute("flex", "1");
  3621.  
  3622.     let consoleCommandSet = this.makeXULNode("commandset");
  3623.     outerWrap.appendChild(consoleCommandSet);
  3624.  
  3625.     let consoleWrap = this.makeXULNode("vbox");
  3626.     this.consoleWrap = consoleWrap;
  3627.     consoleWrap.setAttribute("class", "hud-console-wrapper");
  3628.     consoleWrap.setAttribute("flex", "1");
  3629.  
  3630.     this.filterSpacer = this.makeXULNode("spacer");
  3631.     this.filterSpacer.setAttribute("flex", "1");
  3632.  
  3633.     this.filterBox = this.makeXULNode("textbox");
  3634.     this.filterBox.setAttribute("class", "compact hud-filter-box");
  3635.     this.filterBox.setAttribute("hudId", this.hudId);
  3636.     this.filterBox.setAttribute("placeholder", this.getStr("stringFilter"));
  3637.     this.filterBox.setAttribute("type", "search");
  3638.  
  3639.     this.setFilterTextBoxEvents();
  3640.  
  3641.     this.createConsoleMenu(this.consoleWrap);
  3642.  
  3643.     this.filterPrefs = HUDService.getDefaultFilterPrefs(this.hudId);
  3644.  
  3645.     let consoleFilterToolbar = this.makeFilterToolbar();
  3646.     consoleFilterToolbar.setAttribute("id", "viewGroup");
  3647.     this.consoleFilterToolbar = consoleFilterToolbar;
  3648.  
  3649.     let hintSpacerNode = this.makeXULNode("box");
  3650.     hintSpacerNode.setAttribute("flex", 1);
  3651.  
  3652.     this.hintNode = this.makeXULNode("div");
  3653.     this.hintNode.setAttribute("class", "gcliterm-hint-node");
  3654.  
  3655.     let hintParentNode = this.makeXULNode("vbox");
  3656.     hintParentNode.setAttribute("flex", "0");
  3657.     hintParentNode.setAttribute("class", "gcliterm-hint-parent");
  3658.     hintParentNode.appendChild(hintSpacerNode);
  3659.     hintParentNode.appendChild(this.hintNode);
  3660.     hintParentNode.hidden = true;
  3661.  
  3662.     let hbox = this.makeXULNode("hbox");
  3663.     hbox.setAttribute("flex", "1");
  3664.  
  3665.     this.outputNode = this.makeXULNode("richlistbox");
  3666.     this.outputNode.setAttribute("class", "hud-output-node");
  3667.     this.outputNode.setAttribute("flex", "1");
  3668.     this.outputNode.setAttribute("orient", "vertical");
  3669.     this.outputNode.setAttribute("context", this.hudId + "-output-contextmenu");
  3670.     this.outputNode.setAttribute("style", "direction: ltr;");
  3671.     this.outputNode.setAttribute("seltype", "multiple");
  3672.  
  3673.     hbox.appendChild(hintParentNode);
  3674.     hbox.appendChild(this.outputNode);
  3675.  
  3676.     consoleWrap.appendChild(consoleFilterToolbar);
  3677.     consoleWrap.appendChild(hbox);
  3678.  
  3679.     outerWrap.appendChild(consoleWrap);
  3680.  
  3681.     this.HUDBox.lastTimestamp = 0;
  3682.  
  3683.     this.jsTermParentNode = outerWrap;
  3684.     this.HUDBox.appendChild(outerWrap);
  3685.  
  3686.     return this.HUDBox;
  3687.   },
  3688.  
  3689.   /**
  3690.    * sets the click events for all binary toggle filter buttons
  3691.    *
  3692.    * @returns void
  3693.    */
  3694.   setFilterTextBoxEvents: function HUD_setFilterTextBoxEvents()
  3695.   {
  3696.     var filterBox = this.filterBox;
  3697.     function onChange()
  3698.     {
  3699.       // To improve responsiveness, we let the user finish typing before we
  3700.       // perform the search.
  3701.  
  3702.       if (this.timer == null) {
  3703.         let timerClass = Cc["@mozilla.org/timer;1"];
  3704.         this.timer = timerClass.createInstance(Ci.nsITimer);
  3705.       } else {
  3706.         this.timer.cancel();
  3707.       }
  3708.  
  3709.       let timerEvent = {
  3710.         notify: function setFilterTextBoxEvents_timerEvent_notify() {
  3711.           HUDService.updateFilterText(filterBox);
  3712.         }
  3713.       };
  3714.  
  3715.       this.timer.initWithCallback(timerEvent, SEARCH_DELAY,
  3716.         Ci.nsITimer.TYPE_ONE_SHOT);
  3717.     }
  3718.  
  3719.     filterBox.addEventListener("command", onChange, false);
  3720.     filterBox.addEventListener("input", onChange, false);
  3721.   },
  3722.  
  3723.   /**
  3724.    * Make the filter toolbar where we can toggle logging filters
  3725.    *
  3726.    * @returns nsIDOMNode
  3727.    */
  3728.   makeFilterToolbar: function HUD_makeFilterToolbar()
  3729.   {
  3730.     const BUTTONS = [
  3731.       {
  3732.         name: "PageNet",
  3733.         category: "net",
  3734.         severities: [
  3735.           { name: "ConsoleErrors", prefKey: "network" },
  3736.           { name: "ConsoleLog", prefKey: "networkinfo" }
  3737.         ]
  3738.       },
  3739.       {
  3740.         name: "PageCSS",
  3741.         category: "css",
  3742.         severities: [
  3743.           { name: "ConsoleErrors", prefKey: "csserror" },
  3744.           { name: "ConsoleWarnings", prefKey: "cssparser" }
  3745.         ]
  3746.       },
  3747.       {
  3748.         name: "PageJS",
  3749.         category: "js",
  3750.         severities: [
  3751.           { name: "ConsoleErrors", prefKey: "exception" },
  3752.           { name: "ConsoleWarnings", prefKey: "jswarn" }
  3753.         ]
  3754.       },
  3755.       {
  3756.         name: "PageWebDeveloper",
  3757.         category: "webdev",
  3758.         severities: [
  3759.           { name: "ConsoleErrors", prefKey: "error" },
  3760.           { name: "ConsoleWarnings", prefKey: "warn" },
  3761.           { name: "ConsoleInfo", prefKey: "info" },
  3762.           { name: "ConsoleLog", prefKey: "log" }
  3763.         ]
  3764.       }
  3765.     ];
  3766.  
  3767.     let toolbar = this.makeXULNode("toolbar");
  3768.     toolbar.setAttribute("class", "hud-console-filter-toolbar");
  3769.     toolbar.setAttribute("mode", "full");
  3770.  
  3771.  
  3772.     for (let i = 0; i < BUTTONS.length; i++) {
  3773.       this.makeFilterButton(toolbar, BUTTONS[i]);
  3774.     }
  3775.  
  3776.     toolbar.appendChild(this.filterSpacer);
  3777.  
  3778.     let positionUI = this.createPositionUI();
  3779.     toolbar.appendChild(positionUI);
  3780.  
  3781.     toolbar.appendChild(this.filterBox);
  3782.     this.makeClearConsoleButton(toolbar);
  3783.  
  3784.     this.makeCloseButton(toolbar);
  3785.  
  3786.     return toolbar;
  3787.   },
  3788.  
  3789.   /**
  3790.    * Creates the UI for re-positioning the console
  3791.    *
  3792.    * @return nsIDOMNode
  3793.    *         The toolbarbutton which holds the menu that allows the user to
  3794.    *         change the console position.
  3795.    */
  3796.   createPositionUI: function HUD_createPositionUI()
  3797.   {
  3798.     this._positionConsoleAbove = (function HUD_positionAbove() {
  3799.       this.positionConsole("above");
  3800.     }).bind(this);
  3801.  
  3802.     this._positionConsoleBelow = (function HUD_positionBelow() {
  3803.       this.positionConsole("below");
  3804.     }).bind(this);
  3805.     this._positionConsoleWindow = (function HUD_positionWindow() {
  3806.       this.positionConsole("window");
  3807.     }).bind(this);
  3808.  
  3809.     let button = this.makeXULNode("toolbarbutton");
  3810.     button.setAttribute("type", "menu");
  3811.     button.setAttribute("label", this.getStr("webConsolePosition"));
  3812.     button.setAttribute("tooltip", this.getStr("webConsolePositionTooltip"));
  3813.  
  3814.     let menuPopup = this.makeXULNode("menupopup");
  3815.     button.appendChild(menuPopup);
  3816.  
  3817.     let itemAbove = this.makeXULNode("menuitem");
  3818.     itemAbove.setAttribute("label", this.getStr("webConsolePositionAbove"));
  3819.     itemAbove.setAttribute("type", "checkbox");
  3820.     itemAbove.setAttribute("autocheck", "false");
  3821.     itemAbove.addEventListener("command", this._positionConsoleAbove, false);
  3822.     menuPopup.appendChild(itemAbove);
  3823.  
  3824.     let itemBelow = this.makeXULNode("menuitem");
  3825.     itemBelow.setAttribute("label", this.getStr("webConsolePositionBelow"));
  3826.     itemBelow.setAttribute("type", "checkbox");
  3827.     itemBelow.setAttribute("autocheck", "false");
  3828.     itemBelow.addEventListener("command", this._positionConsoleBelow, false);
  3829.     menuPopup.appendChild(itemBelow);
  3830.  
  3831.     let itemWindow = this.makeXULNode("menuitem");
  3832.     itemWindow.setAttribute("label", this.getStr("webConsolePositionWindow"));
  3833.     itemWindow.setAttribute("type", "checkbox");
  3834.     itemWindow.setAttribute("autocheck", "false");
  3835.     itemWindow.addEventListener("command", this._positionConsoleWindow, false);
  3836.     menuPopup.appendChild(itemWindow);
  3837.  
  3838.     this.positionMenuitems = {
  3839.       last: null,
  3840.       above: itemAbove,
  3841.       below: itemBelow,
  3842.       window: itemWindow,
  3843.     };
  3844.  
  3845.     return button;
  3846.   },
  3847.  
  3848.   /**
  3849.    * Creates the context menu on the console.
  3850.    *
  3851.    * @param nsIDOMNode aOutputNode
  3852.    *        The console output DOM node.
  3853.    * @returns void
  3854.    */
  3855.   createConsoleMenu: function HUD_createConsoleMenu(aConsoleWrapper) {
  3856.     let menuPopup = this.makeXULNode("menupopup");
  3857.     let id = this.hudId + "-output-contextmenu";
  3858.     menuPopup.setAttribute("id", id);
  3859.     menuPopup.addEventListener("popupshowing", function() {
  3860.       saveBodiesItem.setAttribute("checked",
  3861.         HUDService.saveRequestAndResponseBodies);
  3862.     }, true);
  3863.  
  3864.     let saveBodiesItem = this.makeXULNode("menuitem");
  3865.     saveBodiesItem.setAttribute("label", this.getStr("saveBodies.label"));
  3866.     saveBodiesItem.setAttribute("accesskey",
  3867.                                  this.getStr("saveBodies.accesskey"));
  3868.     saveBodiesItem.setAttribute("type", "checkbox");
  3869.     saveBodiesItem.setAttribute("buttonType", "saveBodies");
  3870.     saveBodiesItem.setAttribute("oncommand", "HUDConsoleUI.command(this);");
  3871.     menuPopup.appendChild(saveBodiesItem);
  3872.  
  3873.     menuPopup.appendChild(this.makeXULNode("menuseparator"));
  3874.  
  3875.     let copyItem = this.makeXULNode("menuitem");
  3876.     copyItem.setAttribute("label", this.getStr("copyCmd.label"));
  3877.     copyItem.setAttribute("accesskey", this.getStr("copyCmd.accesskey"));
  3878.     copyItem.setAttribute("key", "key_copy");
  3879.     copyItem.setAttribute("command", "cmd_copy");
  3880.     copyItem.setAttribute("buttonType", "copy");
  3881.     menuPopup.appendChild(copyItem);
  3882.  
  3883.     let selectAllItem = this.makeXULNode("menuitem");
  3884.     selectAllItem.setAttribute("label", this.getStr("selectAllCmd.label"));
  3885.     selectAllItem.setAttribute("accesskey",
  3886.                                this.getStr("selectAllCmd.accesskey"));
  3887.     selectAllItem.setAttribute("hudId", this.hudId);
  3888.     selectAllItem.setAttribute("buttonType", "selectAll");
  3889.     selectAllItem.setAttribute("oncommand", "HUDConsoleUI.command(this);");
  3890.     menuPopup.appendChild(selectAllItem);
  3891.  
  3892.     aConsoleWrapper.appendChild(menuPopup);
  3893.     aConsoleWrapper.setAttribute("context", id);
  3894.   },
  3895.  
  3896.   /**
  3897.    * Creates one of the filter buttons on the toolbar.
  3898.    *
  3899.    * @param nsIDOMNode aParent
  3900.    *        The node to which the filter button should be appended.
  3901.    * @param object aDescriptor
  3902.    *        A descriptor that contains info about the button. Contains "name",
  3903.    *        "category", and "prefKey" properties, and optionally a "severities"
  3904.    *        property.
  3905.    * @return void
  3906.    */
  3907.   makeFilterButton: function HUD_makeFilterButton(aParent, aDescriptor)
  3908.   {
  3909.     let toolbarButton = this.makeXULNode("toolbarbutton");
  3910.     aParent.appendChild(toolbarButton);
  3911.  
  3912.     let toggleFilter = HeadsUpDisplayUICommands.toggleFilter;
  3913.     toolbarButton.addEventListener("click", toggleFilter, false);
  3914.  
  3915.     let name = aDescriptor.name;
  3916.     toolbarButton.setAttribute("type", "menu-button");
  3917.     toolbarButton.setAttribute("label", this.getStr("btn" + name));
  3918.     toolbarButton.setAttribute("tooltip", this.getStr("tip" + name));
  3919.     toolbarButton.setAttribute("category", aDescriptor.category);
  3920.     toolbarButton.setAttribute("hudId", this.hudId);
  3921.     toolbarButton.classList.add("webconsole-filter-button");
  3922.  
  3923.     let menuPopup = this.makeXULNode("menupopup");
  3924.     toolbarButton.appendChild(menuPopup);
  3925.  
  3926.     let allChecked = true;
  3927.     for (let i = 0; i < aDescriptor.severities.length; i++) {
  3928.       let severity = aDescriptor.severities[i];
  3929.       let menuItem = this.makeXULNode("menuitem");
  3930.       menuItem.setAttribute("label", this.getStr("btn" + severity.name));
  3931.       menuItem.setAttribute("type", "checkbox");
  3932.       menuItem.setAttribute("autocheck", "false");
  3933.       menuItem.setAttribute("hudId", this.hudId);
  3934.  
  3935.       let prefKey = severity.prefKey;
  3936.       menuItem.setAttribute("prefKey", prefKey);
  3937.  
  3938.       let checked = this.filterPrefs[prefKey];
  3939.       menuItem.setAttribute("checked", checked);
  3940.       if (!checked) {
  3941.         allChecked = false;
  3942.       }
  3943.  
  3944.       menuItem.addEventListener("command", toggleFilter, false);
  3945.  
  3946.       menuPopup.appendChild(menuItem);
  3947.     }
  3948.  
  3949.     toolbarButton.setAttribute("checked", allChecked);
  3950.   },
  3951.  
  3952.   /**
  3953.    * Creates the close button on the toolbar.
  3954.    *
  3955.    * @param nsIDOMNode aParent
  3956.    *        The toolbar to attach the close button to.
  3957.    * @return void
  3958.    */
  3959.   makeCloseButton: function HUD_makeCloseButton(aToolbar)
  3960.   {
  3961.     this.closeButtonOnCommand = (function HUD_closeButton_onCommand() {
  3962.       HUDService.animate(this.hudId, ANIMATE_OUT, (function() {
  3963.         HUDService.deactivateHUDForContext(this.tab, true);
  3964.       }).bind(this));
  3965.     }).bind(this);
  3966.  
  3967.     this.closeButton = this.makeXULNode("toolbarbutton");
  3968.     this.closeButton.classList.add("webconsole-close-button");
  3969.     this.closeButton.addEventListener("command",
  3970.       this.closeButtonOnCommand, false);
  3971.     aToolbar.appendChild(this.closeButton);
  3972.   },
  3973.  
  3974.   /**
  3975.    * Creates the "Clear Console" button.
  3976.    *
  3977.    * @param nsIDOMNode aParent
  3978.    *        The toolbar to attach the "Clear Console" button to.
  3979.    * @param string aHUDId
  3980.    *        The ID of the console.
  3981.    * @return void
  3982.    */
  3983.   makeClearConsoleButton: function HUD_makeClearConsoleButton(aToolbar)
  3984.   {
  3985.     let hudId = this.hudId;
  3986.     function HUD_clearButton_onCommand() {
  3987.       let hud = HUDService.getHudReferenceById(hudId);
  3988.       if (hud.jsterm) {
  3989.         hud.jsterm.clearOutput();
  3990.       }
  3991.       if (hud.gcliterm) {
  3992.         hud.gcliterm.clearOutput();
  3993.       }
  3994.     }
  3995.  
  3996.     let clearButton = this.makeXULNode("toolbarbutton");
  3997.     clearButton.setAttribute("label", this.getStr("btnClear"));
  3998.     clearButton.classList.add("webconsole-clear-console-button");
  3999.     clearButton.addEventListener("command", HUD_clearButton_onCommand, false);
  4000.  
  4001.     aToolbar.appendChild(clearButton);
  4002.   },
  4003.  
  4004.   /**
  4005.    * Destroy the property inspector message node. This performs the necessary
  4006.    * cleanup for the tree widget and removes it from the DOM.
  4007.    *
  4008.    * @param nsIDOMNode aMessageNode
  4009.    *        The message node that contains the property inspector from a
  4010.    *        console.dir call.
  4011.    */
  4012.   pruneConsoleDirNode: function HUD_pruneConsoleDirNode(aMessageNode)
  4013.   {
  4014.     aMessageNode.parentNode.removeChild(aMessageNode);
  4015.     let tree = aMessageNode.querySelector("tree");
  4016.     tree.parentNode.removeChild(tree);
  4017.     aMessageNode.propertyTreeView = null;
  4018.     tree.view = null;
  4019.     tree = null;
  4020.   },
  4021.  
  4022.   /**
  4023.    * Create the Web Console UI.
  4024.    *
  4025.    * @return nsIDOMNode
  4026.    *         The Web Console container element (HUDBox).
  4027.    */
  4028.   createHUD: function HUD_createHUD()
  4029.   {
  4030.     if (!this.HUDBox) {
  4031.       this.makeHUDNodes();
  4032.       let positionPref = Services.prefs.getCharPref("devtools.webconsole.position");
  4033.       this.positionConsole(positionPref);
  4034.     }
  4035.     return this.HUDBox;
  4036.   },
  4037.  
  4038.   uiInOwnWindow: false,
  4039.  
  4040.   get console() { return this.contentWindow.wrappedJSObject.console; },
  4041.  
  4042.   getLogCount: function HUD_getLogCount()
  4043.   {
  4044.     return this.outputNode.childNodes.length;
  4045.   },
  4046.  
  4047.   getLogNodes: function HUD_getLogNodes()
  4048.   {
  4049.     return this.outputNode.childNodes;
  4050.   },
  4051.  
  4052.   ERRORS: {
  4053.     HUD_BOX_DOES_NOT_EXIST: "Heads Up Display does not exist",
  4054.     TAB_ID_REQUIRED: "Tab DOM ID is required",
  4055.     PARENTNODE_NOT_FOUND: "parentNode element not found"
  4056.   },
  4057.  
  4058.   /**
  4059.    * Destroy the HUD object. Call this method to avoid memory leaks when the Web
  4060.    * Console is closed.
  4061.    */
  4062.   destroy: function HUD_destroy()
  4063.   {
  4064.     if (this.jsterm) {
  4065.       this.jsterm.destroy();
  4066.     }
  4067.     if (this.gcliterm) {
  4068.       this.gcliterm.destroy();
  4069.     }
  4070.  
  4071.     this.positionMenuitems.above.removeEventListener("command",
  4072.       this._positionConsoleAbove, false);
  4073.     this.positionMenuitems.below.removeEventListener("command",
  4074.       this._positionConsoleBelow, false);
  4075.     this.positionMenuitems.window.removeEventListener("command",
  4076.       this._positionConsoleWindow, false);
  4077.  
  4078.     this.closeButton.removeEventListener("command",
  4079.       this.closeButtonOnCommand, false);
  4080.   },
  4081. };
  4082.  
  4083.  
  4084. //////////////////////////////////////////////////////////////////////////////
  4085. // ConsoleAPIObserver
  4086. //////////////////////////////////////////////////////////////////////////////
  4087.  
  4088. let ConsoleAPIObserver = {
  4089.  
  4090.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
  4091.  
  4092.   init: function CAO_init()
  4093.   {
  4094.     Services.obs.addObserver(this, "quit-application-granted", false);
  4095.     Services.obs.addObserver(this, "console-api-log-event", false);
  4096.   },
  4097.  
  4098.   observe: function CAO_observe(aMessage, aTopic, aData)
  4099.   {
  4100.     if (aTopic == "console-api-log-event") {
  4101.       aMessage = aMessage.wrappedJSObject;
  4102.       let windowId = parseInt(aData);
  4103.       let win = HUDService.getWindowByWindowId(windowId);
  4104.       if (!win)
  4105.         return;
  4106.  
  4107.       // Find the HUD ID for the topmost window
  4108.       let hudId = HUDService.getHudIdByWindow(win.top);
  4109.       if (!hudId)
  4110.         return;
  4111.  
  4112.       HUDService.logConsoleAPIMessage(hudId, aMessage);
  4113.     }
  4114.     else if (aTopic == "quit-application-granted") {
  4115.       HUDService.shutdown();
  4116.     }
  4117.   },
  4118.  
  4119.   shutdown: function CAO_shutdown()
  4120.   {
  4121.     Services.obs.removeObserver(this, "quit-application-granted");
  4122.     Services.obs.removeObserver(this, "console-api-log-event");
  4123.   }
  4124. };
  4125.  
  4126. /**
  4127.  * Creates a DOM Node factory for XUL nodes - as well as textNodes
  4128.  * @param aFactoryType "xul" or "text"
  4129.  * @param ignored This parameter is currently ignored, and will be removed
  4130.  * See bug 594304
  4131.  * @param aDocument The document, the factory is to generate nodes from
  4132.  * @return DOM Node Factory function
  4133.  */
  4134. function NodeFactory(aFactoryType, ignored, aDocument)
  4135. {
  4136.   // aDocument is presumed to be a XULDocument
  4137.   if (aFactoryType == "text") {
  4138.     return function factory(aText)
  4139.     {
  4140.       return aDocument.createTextNode(aText);
  4141.     }
  4142.   }
  4143.   else if (aFactoryType == "xul") {
  4144.     return function factory(aTag)
  4145.     {
  4146.       return aDocument.createElement(aTag);
  4147.     }
  4148.   }
  4149.   else {
  4150.     throw new Error('NodeFactory: Unknown factory type: ' + aFactoryType);
  4151.   }
  4152. }
  4153.  
  4154. //////////////////////////////////////////////////////////////////////////
  4155. // JS Completer
  4156. //////////////////////////////////////////////////////////////////////////
  4157.  
  4158. const STATE_NORMAL = 0;
  4159. const STATE_QUOTE = 2;
  4160. const STATE_DQUOTE = 3;
  4161.  
  4162. const OPEN_BODY = '{[('.split('');
  4163. const CLOSE_BODY = '}])'.split('');
  4164. const OPEN_CLOSE_BODY = {
  4165.   '{': '}',
  4166.   '[': ']',
  4167.   '(': ')'
  4168. };
  4169.  
  4170. /**
  4171.  * Analyses a given string to find the last statement that is interesting for
  4172.  * later completion.
  4173.  *
  4174.  * @param   string aStr
  4175.  *          A string to analyse.
  4176.  *
  4177.  * @returns object
  4178.  *          If there was an error in the string detected, then a object like
  4179.  *
  4180.  *            { err: "ErrorMesssage" }
  4181.  *
  4182.  *          is returned, otherwise a object like
  4183.  *
  4184.  *            {
  4185.  *              state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
  4186.  *              startPos: index of where the last statement begins
  4187.  *            }
  4188.  */
  4189. function findCompletionBeginning(aStr)
  4190. {
  4191.   let bodyStack = [];
  4192.  
  4193.   let state = STATE_NORMAL;
  4194.   let start = 0;
  4195.   let c;
  4196.   for (let i = 0; i < aStr.length; i++) {
  4197.     c = aStr[i];
  4198.  
  4199.     switch (state) {
  4200.       // Normal JS state.
  4201.       case STATE_NORMAL:
  4202.         if (c == '"') {
  4203.           state = STATE_DQUOTE;
  4204.         }
  4205.         else if (c == '\'') {
  4206.           state = STATE_QUOTE;
  4207.         }
  4208.         else if (c == ';') {
  4209.           start = i + 1;
  4210.         }
  4211.         else if (c == ' ') {
  4212.           start = i + 1;
  4213.         }
  4214.         else if (OPEN_BODY.indexOf(c) != -1) {
  4215.           bodyStack.push({
  4216.             token: c,
  4217.             start: start
  4218.           });
  4219.           start = i + 1;
  4220.         }
  4221.         else if (CLOSE_BODY.indexOf(c) != -1) {
  4222.           var last = bodyStack.pop();
  4223.           if (!last || OPEN_CLOSE_BODY[last.token] != c) {
  4224.             return {
  4225.               err: "syntax error"
  4226.             };
  4227.           }
  4228.           if (c == '}') {
  4229.             start = i + 1;
  4230.           }
  4231.           else {
  4232.             start = last.start;
  4233.           }
  4234.         }
  4235.         break;
  4236.  
  4237.       // Double quote state > " <
  4238.       case STATE_DQUOTE:
  4239.         if (c == '\\') {
  4240.           i ++;
  4241.         }
  4242.         else if (c == '\n') {
  4243.           return {
  4244.             err: "unterminated string literal"
  4245.           };
  4246.         }
  4247.         else if (c == '"') {
  4248.           state = STATE_NORMAL;
  4249.         }
  4250.         break;
  4251.  
  4252.       // Single quoate state > ' <
  4253.       case STATE_QUOTE:
  4254.         if (c == '\\') {
  4255.           i ++;
  4256.         }
  4257.         else if (c == '\n') {
  4258.           return {
  4259.             err: "unterminated string literal"
  4260.           };
  4261.           return;
  4262.         }
  4263.         else if (c == '\'') {
  4264.           state = STATE_NORMAL;
  4265.         }
  4266.         break;
  4267.     }
  4268.   }
  4269.  
  4270.   return {
  4271.     state: state,
  4272.     startPos: start
  4273.   };
  4274. }
  4275.  
  4276. /**
  4277.  * Provides a list of properties, that are possible matches based on the passed
  4278.  * scope and inputValue.
  4279.  *
  4280.  * @param object aScope
  4281.  *        Scope to use for the completion.
  4282.  *
  4283.  * @param string aInputValue
  4284.  *        Value that should be completed.
  4285.  *
  4286.  * @returns null or object
  4287.  *          If no completion valued could be computed, null is returned,
  4288.  *          otherwise a object with the following form is returned:
  4289.  *            {
  4290.  *              matches: [ string, string, string ],
  4291.  *              matchProp: Last part of the inputValue that was used to find
  4292.  *                         the matches-strings.
  4293.  *            }
  4294.  */
  4295. function JSPropertyProvider(aScope, aInputValue)
  4296. {
  4297.   let obj = unwrap(aScope);
  4298.  
  4299.   // Analyse the aInputValue and find the beginning of the last part that
  4300.   // should be completed.
  4301.   let beginning = findCompletionBeginning(aInputValue);
  4302.  
  4303.   // There was an error analysing the string.
  4304.   if (beginning.err) {
  4305.     return null;
  4306.   }
  4307.  
  4308.   // If the current state is not STATE_NORMAL, then we are inside of an string
  4309.   // which means that no completion is possible.
  4310.   if (beginning.state != STATE_NORMAL) {
  4311.     return null;
  4312.   }
  4313.  
  4314.   let completionPart = aInputValue.substring(beginning.startPos);
  4315.  
  4316.   // Don't complete on just an empty string.
  4317.   if (completionPart.trim() == "") {
  4318.     return null;
  4319.   }
  4320.  
  4321.   let properties = completionPart.split('.');
  4322.   let matchProp;
  4323.   if (properties.length > 1) {
  4324.     matchProp = properties.pop().trimLeft();
  4325.     for (let i = 0; i < properties.length; i++) {
  4326.       let prop = properties[i].trim();
  4327.  
  4328.       // If obj is undefined or null, then there is no chance to run completion
  4329.       // on it. Exit here.
  4330.       if (typeof obj === "undefined" || obj === null) {
  4331.         return null;
  4332.       }
  4333.  
  4334.       // Check if prop is a getter function on obj. Functions can change other
  4335.       // stuff so we can't execute them to get the next object. Stop here.
  4336.       if (isNonNativeGetter(obj, prop)) {
  4337.         return null;
  4338.       }
  4339.       try {
  4340.         obj = obj[prop];
  4341.       }
  4342.       catch (ex) {
  4343.         return null;
  4344.       }
  4345.     }
  4346.   }
  4347.   else {
  4348.     matchProp = properties[0].trimLeft();
  4349.   }
  4350.  
  4351.   // If obj is undefined or null, then there is no chance to run
  4352.   // completion on it. Exit here.
  4353.   if (typeof obj === "undefined" || obj === null) {
  4354.     return null;
  4355.   }
  4356.  
  4357.   // Skip Iterators and Generators.
  4358.   if (isIteratorOrGenerator(obj)) {
  4359.     return null;
  4360.   }
  4361.  
  4362.   let matches = [];
  4363.   for (let prop in obj) {
  4364.     if (prop.indexOf(matchProp) == 0) {
  4365.       matches.push(prop);
  4366.     }
  4367.   }
  4368.  
  4369.   return {
  4370.     matchProp: matchProp,
  4371.     matches: matches.sort(),
  4372.   };
  4373. }
  4374.  
  4375. function isIteratorOrGenerator(aObject)
  4376. {
  4377.   if (aObject === null) {
  4378.     return false;
  4379.   }
  4380.  
  4381.   if (typeof aObject == "object") {
  4382.     if (typeof aObject.__iterator__ == "function" ||
  4383.         aObject.constructor && aObject.constructor.name == "Iterator") {
  4384.       return true;
  4385.     }
  4386.  
  4387.     try {
  4388.       let str = aObject.toString();
  4389.       if (typeof aObject.next == "function" &&
  4390.           str.indexOf("[object Generator") == 0) {
  4391.         return true;
  4392.       }
  4393.     }
  4394.     catch (ex) {
  4395.       // window.history.next throws in the typeof check above.
  4396.       return false;
  4397.     }
  4398.   }
  4399.  
  4400.   return false;
  4401. }
  4402.  
  4403. //////////////////////////////////////////////////////////////////////////
  4404. // JSTerm
  4405. //////////////////////////////////////////////////////////////////////////
  4406.  
  4407. /**
  4408.  * JSTermHelper
  4409.  *
  4410.  * Defines a set of functions ("helper functions") that are available from the
  4411.  * WebConsole but not from the webpage.
  4412.  * A list of helper functions used by Firebug can be found here:
  4413.  *   http://getfirebug.com/wiki/index.php/Command_Line_API
  4414.  */
  4415. function JSTermHelper(aJSTerm)
  4416. {
  4417.   /**
  4418.    * Returns the result of document.getElementById(aId).
  4419.    *
  4420.    * @param string aId
  4421.    *        A string that is passed to window.document.getElementById.
  4422.    * @returns nsIDOMNode or null
  4423.    */
  4424.   aJSTerm.sandbox.$ = function JSTH_$(aId)
  4425.   {
  4426.     try {
  4427.       return aJSTerm._window.document.getElementById(aId);
  4428.     }
  4429.     catch (ex) {
  4430.       aJSTerm.console.error(ex.message);
  4431.     }
  4432.   };
  4433.  
  4434.   /**
  4435.    * Returns the result of document.querySelectorAll(aSelector).
  4436.    *
  4437.    * @param string aSelector
  4438.    *        A string that is passed to window.document.querySelectorAll.
  4439.    * @returns array of nsIDOMNode
  4440.    */
  4441.   aJSTerm.sandbox.$$ = function JSTH_$$(aSelector)
  4442.   {
  4443.     try {
  4444.       return aJSTerm._window.document.querySelectorAll(aSelector);
  4445.     }
  4446.     catch (ex) {
  4447.       aJSTerm.console.error(ex.message);
  4448.     }
  4449.   };
  4450.  
  4451.   /**
  4452.    * Runs a xPath query and returns all matched nodes.
  4453.    *
  4454.    * @param string aXPath
  4455.    *        xPath search query to execute.
  4456.    * @param [optional] nsIDOMNode aContext
  4457.    *        Context to run the xPath query on. Uses window.document if not set.
  4458.    * @returns array of nsIDOMNode
  4459.    */
  4460.   aJSTerm.sandbox.$x = function JSTH_$x(aXPath, aContext)
  4461.   {
  4462.     let nodes = [];
  4463.     let doc = aJSTerm._window.document;
  4464.     let aContext = aContext || doc;
  4465.  
  4466.     try {
  4467.       let results = doc.evaluate(aXPath, aContext, null,
  4468.                                   Ci.nsIDOMXPathResult.ANY_TYPE, null);
  4469.  
  4470.       let node;
  4471.       while (node = results.iterateNext()) {
  4472.         nodes.push(node);
  4473.       }
  4474.     }
  4475.     catch (ex) {
  4476.       aJSTerm.console.error(ex.message);
  4477.     }
  4478.  
  4479.     return nodes;
  4480.   };
  4481.  
  4482.   /**
  4483.    * Returns the currently selected object in the highlighter.
  4484.    *
  4485.    * @returns nsIDOMNode or null
  4486.    */
  4487.   Object.defineProperty(aJSTerm.sandbox, "$0", {
  4488.     get: function() {
  4489.       let mw = HUDService.currentContext();
  4490.       try {
  4491.         return mw.InspectorUI.selection;
  4492.       }
  4493.       catch (ex) {
  4494.         aJSTerm.console.error(ex.message);
  4495.       }
  4496.     },
  4497.     enumerable: true,
  4498.     configurable: false
  4499.   });
  4500.  
  4501.   /**
  4502.    * Clears the output of the JSTerm.
  4503.    */
  4504.   aJSTerm.sandbox.clear = function JSTH_clear()
  4505.   {
  4506.     aJSTerm.helperEvaluated = true;
  4507.     aJSTerm.clearOutput();
  4508.   };
  4509.  
  4510.   /**
  4511.    * Returns the result of Object.keys(aObject).
  4512.    *
  4513.    * @param object aObject
  4514.    *        Object to return the property names from.
  4515.    * @returns array of string
  4516.    */
  4517.   aJSTerm.sandbox.keys = function JSTH_keys(aObject)
  4518.   {
  4519.     try {
  4520.       return Object.keys(unwrap(aObject));
  4521.     }
  4522.     catch (ex) {
  4523.       aJSTerm.console.error(ex.message);
  4524.     }
  4525.   };
  4526.  
  4527.   /**
  4528.    * Returns the values of all properties on aObject.
  4529.    *
  4530.    * @param object aObject
  4531.    *        Object to display the values from.
  4532.    * @returns array of string
  4533.    */
  4534.   aJSTerm.sandbox.values = function JSTH_values(aObject)
  4535.   {
  4536.     let arrValues = [];
  4537.     let obj = unwrap(aObject);
  4538.  
  4539.     try {
  4540.       for (let prop in obj) {
  4541.         arrValues.push(obj[prop]);
  4542.       }
  4543.     }
  4544.     catch (ex) {
  4545.       aJSTerm.console.error(ex.message);
  4546.     }
  4547.     return arrValues;
  4548.   };
  4549.  
  4550.   /**
  4551.    * Opens a help window in MDC
  4552.    */
  4553.   aJSTerm.sandbox.help = function JSTH_help()
  4554.   {
  4555.     aJSTerm.helperEvaluated = true;
  4556.     aJSTerm._window.open(
  4557.         "https://developer.mozilla.org/AppLinks/WebConsoleHelp?locale=" +
  4558.         aJSTerm._window.navigator.language, "help", "");
  4559.   };
  4560.  
  4561.   /**
  4562.    * Inspects the passed aObject. This is done by opening the PropertyPanel.
  4563.    *
  4564.    * @param object aObject
  4565.    *        Object to inspect.
  4566.    * @returns void
  4567.    */
  4568.   aJSTerm.sandbox.inspect = function JSTH_inspect(aObject)
  4569.   {
  4570.     aJSTerm.helperEvaluated = true;
  4571.     let propPanel = aJSTerm.openPropertyPanel(null, unwrap(aObject));
  4572.     propPanel.panel.setAttribute("hudId", aJSTerm.hudId);
  4573.   };
  4574.  
  4575.   aJSTerm.sandbox.inspectrules = function JSTH_inspectrules(aNode)
  4576.   {
  4577.     aJSTerm.helperEvaluated = true;
  4578.     let doc = aJSTerm.parentNode.ownerDocument;
  4579.     let win = doc.defaultView;
  4580.     let panel = createElement(doc, "panel", {
  4581.       label: "CSS Rules",
  4582.       titlebar: "normal",
  4583.       noautofocus: "true",
  4584.       noautohide: "true",
  4585.       close: "true",
  4586.       width: 350,
  4587.       height: (win.screen.height / 2)
  4588.     });
  4589.  
  4590.     let iframe = createAndAppendElement(panel, "iframe", {
  4591.       src: "chrome://browser/content/devtools/cssruleview.xul",
  4592.       flex: "1",
  4593.     });
  4594.  
  4595.     panel.addEventListener("load", function onLoad() {
  4596.       panel.removeEventListener("load", onLoad, true);
  4597.       let doc = iframe.contentDocument;
  4598.       let view = new CssRuleView(doc);
  4599.       doc.documentElement.appendChild(view.element);
  4600.       view.highlight(aNode);
  4601.     }, true);
  4602.  
  4603.     let parent = doc.getElementById("mainPopupSet");
  4604.     parent.appendChild(panel);
  4605.  
  4606.     panel.addEventListener("popuphidden", function onHide() {
  4607.       panel.removeEventListener("popuphidden", onHide);
  4608.       parent.removeChild(panel);
  4609.     });
  4610.  
  4611.     let footer = createElement(doc, "hbox", { align: "end" });
  4612.     createAndAppendElement(footer, "spacer", { flex: 1});
  4613.     createAndAppendElement(footer, "resizer", { dir: "bottomend" });
  4614.     panel.appendChild(footer);
  4615.  
  4616.     let anchor = win.gBrowser.selectedBrowser;
  4617.     panel.openPopup(anchor, "end_before", 0, 0, false, false);
  4618.  
  4619.   }
  4620.  
  4621.   /**
  4622.    * Prints aObject to the output.
  4623.    *
  4624.    * @param object aObject
  4625.    *        Object to print to the output.
  4626.    * @returns void
  4627.    */
  4628.   aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
  4629.   {
  4630.     aJSTerm.helperEvaluated = true;
  4631.     if (aObject === null || aObject === undefined || aObject === true || aObject === false) {
  4632.       aJSTerm.console.error(HUDService.getStr("helperFuncUnsupportedTypeError"));
  4633.       return;
  4634.     }
  4635.     else if (typeof aObject === TYPEOF_FUNCTION) {
  4636.       aJSTerm.writeOutput(aObject + "\n", CATEGORY_OUTPUT, SEVERITY_LOG);
  4637.       return;
  4638.     }
  4639.  
  4640.     let output = [];
  4641.     let pairs = namesAndValuesOf(unwrap(aObject));
  4642.  
  4643.     pairs.forEach(function(pair) {
  4644.       output.push("  " + pair.display);
  4645.     });
  4646.  
  4647.     aJSTerm.writeOutput(output.join("\n"), CATEGORY_OUTPUT, SEVERITY_LOG);
  4648.   };
  4649.  
  4650.   /**
  4651.    * Print a string to the output, as-is.
  4652.    *
  4653.    * @param string aString
  4654.    *        A string you want to output.
  4655.    * @returns void
  4656.    */
  4657.   aJSTerm.sandbox.print = function JSTH_print(aString)
  4658.   {
  4659.     aJSTerm.helperEvaluated = true;
  4660.     aJSTerm.writeOutput("" + aString, CATEGORY_OUTPUT, SEVERITY_LOG);
  4661.   };
  4662. }
  4663.  
  4664. /**
  4665.  * JSTerm
  4666.  *
  4667.  * JavaScript Terminal: creates input nodes for console code interpretation
  4668.  * and 'JS Workspaces'
  4669.  */
  4670.  
  4671. /**
  4672.  * Create a JSTerminal or attach a JSTerm input node to an existing output node,
  4673.  * given by the parent node.
  4674.  *
  4675.  * @param object aContext
  4676.  *        Usually nsIDOMWindow, but doesn't have to be
  4677.  * @param nsIDOMNode aParentNode where to attach the JSTerm
  4678.  * @param object aMixin
  4679.  *        Gecko-app (or Jetpack) specific utility object
  4680.  * @param object aConsole
  4681.  *        Console object to use within the JSTerm.
  4682.  */
  4683. function JSTerm(aContext, aParentNode, aMixin, aConsole)
  4684. {
  4685.   // set the context, attach the UI by appending to aParentNode
  4686.  
  4687.   this.application = appName();
  4688.   this.context = aContext;
  4689.   this.parentNode = aParentNode;
  4690.   this.mixins = aMixin;
  4691.   this.console = aConsole;
  4692.  
  4693.   this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
  4694.  
  4695.   let node = aParentNode;
  4696.   while (!node.hasAttribute("id")) {
  4697.     node = node.parentNode;
  4698.   }
  4699.   this.hudId = node.getAttribute("id");
  4700.  
  4701.   this.historyIndex = 0;
  4702.   this.historyPlaceHolder = 0;  // this.history.length;
  4703.   this.log = LogFactory("*** JSTerm:");
  4704.   this.autocompletePopup = new AutocompletePopup(aParentNode.ownerDocument);
  4705.   this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
  4706.   this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
  4707.   this.init();
  4708. }
  4709.  
  4710. JSTerm.prototype = {
  4711.  
  4712.   propertyProvider: JSPropertyProvider,
  4713.  
  4714.   COMPLETE_FORWARD: 0,
  4715.   COMPLETE_BACKWARD: 1,
  4716.   COMPLETE_HINT_ONLY: 2,
  4717.  
  4718.   init: function JST_init()
  4719.   {
  4720.     this.createSandbox();
  4721.  
  4722.     this.inputNode = this.mixins.inputNode;
  4723.     this.outputNode = this.mixins.outputNode;
  4724.     this.completeNode = this.mixins.completeNode;
  4725.  
  4726.     this._keyPress = this.keyPress.bind(this);
  4727.     this._inputEventHandler = this.inputEventHandler.bind(this);
  4728.  
  4729.     this.inputNode.addEventListener("keypress",
  4730.       this._keyPress, false);
  4731.     this.inputNode.addEventListener("input",
  4732.       this._inputEventHandler, false);
  4733.     this.inputNode.addEventListener("keyup",
  4734.       this._inputEventHandler, false);
  4735.   },
  4736.  
  4737.   get codeInputString()
  4738.   {
  4739.     return this.inputNode.value;
  4740.   },
  4741.  
  4742.   generateUI: function JST_generateUI()
  4743.   {
  4744.     this.mixins.generateUI();
  4745.   },
  4746.  
  4747.   attachUI: function JST_attachUI()
  4748.   {
  4749.     this.mixins.attachUI();
  4750.   },
  4751.  
  4752.   createSandbox: function JST_setupSandbox()
  4753.   {
  4754.     // create a JS Sandbox out of this.context
  4755.     this.sandbox = new Cu.Sandbox(this._window,
  4756.       { sandboxPrototype: this._window, wantXrays: false });
  4757.     this.sandbox.console = this.console;
  4758.     JSTermHelper(this);
  4759.   },
  4760.  
  4761.   get _window()
  4762.   {
  4763.     return this.context.get().QueryInterface(Ci.nsIDOMWindow);
  4764.   },
  4765.  
  4766.   /**
  4767.    * Evaluates a string in the sandbox.
  4768.    *
  4769.    * @param string aString
  4770.    *        String to evaluate in the sandbox.
  4771.    * @returns something
  4772.    *          The result of the evaluation.
  4773.    */
  4774.   evalInSandbox: function JST_evalInSandbox(aString)
  4775.   {
  4776.     // The help function needs to be easy to guess, so we make the () optional
  4777.     if (aString.trim() === "help" || aString.trim() === "?") {
  4778.       aString = "help()";
  4779.     }
  4780.  
  4781.     let window = unwrap(this.sandbox.window);
  4782.     let $ = null, $$ = null;
  4783.  
  4784.     // We prefer to execute the page-provided implementations for the $() and
  4785.     // $$() functions.
  4786.     if (typeof window.$ == "function") {
  4787.       $ = this.sandbox.$;
  4788.       delete this.sandbox.$;
  4789.     }
  4790.     if (typeof window.$$ == "function") {
  4791.       $$ = this.sandbox.$$;
  4792.       delete this.sandbox.$$;
  4793.     }
  4794.  
  4795.     let result = Cu.evalInSandbox(aString, this.sandbox, "1.8", "Web Console", 1);
  4796.  
  4797.     if ($) {
  4798.       this.sandbox.$ = $;
  4799.     }
  4800.     if ($$) {
  4801.       this.sandbox.$$ = $$;
  4802.     }
  4803.  
  4804.     return result;
  4805.   },
  4806.  
  4807.  
  4808.   execute: function JST_execute(aExecuteString)
  4809.   {
  4810.     // attempt to execute the content of the inputNode
  4811.     aExecuteString = aExecuteString || this.inputNode.value;
  4812.     if (!aExecuteString) {
  4813.       this.writeOutput("no value to execute", CATEGORY_OUTPUT, SEVERITY_LOG);
  4814.       return;
  4815.     }
  4816.  
  4817.     this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG);
  4818.  
  4819.     try {
  4820.       this.helperEvaluated = false;
  4821.       let result = this.evalInSandbox(aExecuteString);
  4822.  
  4823.       // Hide undefined results coming from helpers.
  4824.       let shouldShow = !(result === undefined && this.helperEvaluated);
  4825.       if (shouldShow) {
  4826.         let inspectable = this.isResultInspectable(result);
  4827.         let resultString = this.formatResult(result);
  4828.  
  4829.         if (inspectable) {
  4830.           this.writeOutputJS(aExecuteString, result, resultString);
  4831.         }
  4832.         else {
  4833.           this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG);
  4834.         }
  4835.       }
  4836.     }
  4837.     catch (ex) {
  4838.       this.writeOutput("" + ex, CATEGORY_OUTPUT, SEVERITY_ERROR);
  4839.     }
  4840.  
  4841.     this.history.push(aExecuteString);
  4842.     this.historyIndex++;
  4843.     this.historyPlaceHolder = this.history.length;
  4844.     this.setInputValue("");
  4845.     this.clearCompletion();
  4846.   },
  4847.  
  4848.   /**
  4849.    * Opens a new PropertyPanel. The panel has two buttons: "Update" reexecutes
  4850.    * the passed aEvalString and places the result inside of the tree. The other
  4851.    * button closes the panel.
  4852.    *
  4853.    * @param string aEvalString
  4854.    *        String that was used to eval the aOutputObject. Used as title
  4855.    *        and to update the tree content.
  4856.    * @param object aOutputObject
  4857.    *        Object to display/inspect inside of the tree.
  4858.    * @param nsIDOMNode aAnchor
  4859.    *        A node to popup the panel next to (using "after_pointer").
  4860.    * @returns object the created and opened propertyPanel.
  4861.    */
  4862.   openPropertyPanel: function JST_openPropertyPanel(aEvalString, aOutputObject,
  4863.                                                     aAnchor)
  4864.   {
  4865.     let self = this;
  4866.     let propPanel;
  4867.     // The property panel has one button:
  4868.     //    `Update`: reexecutes the string executed on the command line. The
  4869.     //    result will be inspected by this panel.
  4870.     let buttons = [];
  4871.  
  4872.     // If there is a evalString passed to this function, then add a `Update`
  4873.     // button to the panel so that the evalString can be reexecuted to update
  4874.     // the content of the panel.
  4875.     if (aEvalString !== null) {
  4876.       buttons.push({
  4877.         label: HUDService.getStr("update.button"),
  4878.         accesskey: HUDService.getStr("update.accesskey"),
  4879.         oncommand: function () {
  4880.           try {
  4881.             var result = self.evalInSandbox(aEvalString);
  4882.  
  4883.             if (result !== undefined) {
  4884.               // TODO: This updates the value of the tree.
  4885.               // However, the states of opened nodes is not saved.
  4886.               // See bug 586246.
  4887.               propPanel.treeView.data = result;
  4888.             }
  4889.           }
  4890.           catch (ex) {
  4891.             self.console.error(ex);
  4892.           }
  4893.         }
  4894.       });
  4895.     }
  4896.  
  4897.     let doc = self.parentNode.ownerDocument;
  4898.     let parent = doc.getElementById("mainPopupSet");
  4899.     let title = (aEvalString
  4900.         ? HUDService.getFormatStr("jsPropertyInspectTitle", [aEvalString])
  4901.         : HUDService.getStr("jsPropertyTitle"));
  4902.  
  4903.     propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
  4904.     propPanel.linkNode = aAnchor;
  4905.  
  4906.     let panel = propPanel.panel;
  4907.     panel.openPopup(aAnchor, "after_pointer", 0, 0, false, false);
  4908.     panel.sizeTo(350, 450);
  4909.     return propPanel;
  4910.   },
  4911.  
  4912.   /**
  4913.    * Writes a JS object to the JSTerm outputNode. If the user clicks on the
  4914.    * written object, openPropertyPanel is called to open up a panel to inspect
  4915.    * the object.
  4916.    *
  4917.    * @param string aEvalString
  4918.    *        String that was evaluated to get the aOutputObject.
  4919.    * @param object aResultObject
  4920.    *        The evaluation result object.
  4921.    * @param object aOutputString
  4922.    *        The output string to be written to the outputNode.
  4923.    */
  4924.   writeOutputJS: function JST_writeOutputJS(aEvalString, aOutputObject, aOutputString)
  4925.   {
  4926.     let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument,
  4927.                                               CATEGORY_OUTPUT,
  4928.                                               SEVERITY_LOG,
  4929.                                               aOutputString,
  4930.                                               this.hudId);
  4931.  
  4932.     let linkNode = node.querySelector(".webconsole-msg-body");
  4933.  
  4934.     linkNode.classList.add("hud-clickable");
  4935.     linkNode.setAttribute("aria-haspopup", "true");
  4936.  
  4937.     // Make the object bring up the property panel.
  4938.     node.addEventListener("mousedown", function(aEvent) {
  4939.       this._startX = aEvent.clientX;
  4940.       this._startY = aEvent.clientY;
  4941.     }, false);
  4942.  
  4943.     let self = this;
  4944.     node.addEventListener("click", function(aEvent) {
  4945.       if (aEvent.detail != 1 || aEvent.button != 0 ||
  4946.           (this._startX != aEvent.clientX &&
  4947.            this._startY != aEvent.clientY)) {
  4948.         return;
  4949.       }
  4950.  
  4951.       if (!this._panelOpen) {
  4952.         let propPanel = self.openPropertyPanel(aEvalString, aOutputObject, this);
  4953.         propPanel.panel.setAttribute("hudId", self.hudId);
  4954.         this._panelOpen = true;
  4955.       }
  4956.     }, false);
  4957.  
  4958.     ConsoleUtils.outputMessageNode(node, this.hudId);
  4959.   },
  4960.  
  4961.   /**
  4962.    * Writes a message to the HUD that originates from the interactive
  4963.    * JavaScript console.
  4964.    *
  4965.    * @param string aOutputMessage
  4966.    *        The message to display.
  4967.    * @param number aCategory
  4968.    *        The category of message: one of the CATEGORY_ constants.
  4969.    * @param number aSeverity
  4970.    *        The severity of message: one of the SEVERITY_ constants.
  4971.    * @returns void
  4972.    */
  4973.   writeOutput: function JST_writeOutput(aOutputMessage, aCategory, aSeverity)
  4974.   {
  4975.     let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument,
  4976.                                               aCategory, aSeverity,
  4977.                                               aOutputMessage, this.hudId);
  4978.  
  4979.     ConsoleUtils.outputMessageNode(node, this.hudId);
  4980.   },
  4981.  
  4982.   /**
  4983.    * Format the jsterm execution result based on its type.
  4984.    *
  4985.    * @param mixed aResult
  4986.    *        The evaluation result object you want displayed.
  4987.    * @returns string
  4988.    *          The string that can be displayed.
  4989.    */
  4990.   formatResult: function JST_formatResult(aResult)
  4991.   {
  4992.     let output = "";
  4993.     let type = this.getResultType(aResult);
  4994.  
  4995.     switch (type) {
  4996.       case "string":
  4997.         output = this.formatString(aResult);
  4998.         break;
  4999.       case "boolean":
  5000.       case "date":
  5001.       case "error":
  5002.       case "number":
  5003.       case "regexp":
  5004.         output = aResult.toString();
  5005.         break;
  5006.       case "null":
  5007.       case "undefined":
  5008.         output = type;
  5009.         break;
  5010.       default:
  5011.         try {
  5012.           if (aResult.toSource) {
  5013.             output = aResult.toSource();
  5014.           }
  5015.           if (!output || output == "({})") {
  5016.             output = aResult + "";
  5017.           }
  5018.         }
  5019.         catch (ex) {
  5020.           output = ex;
  5021.         }
  5022.         break;
  5023.     }
  5024.  
  5025.     return output + "";
  5026.   },
  5027.  
  5028.   /**
  5029.    * Format a string for output.
  5030.    *
  5031.    * @param string aString
  5032.    *        The string you want to display.
  5033.    * @returns string
  5034.    *          The string that can be displayed.
  5035.    */
  5036.   formatString: function JST_formatString(aString)
  5037.   {
  5038.     function isControlCode(c) {
  5039.       // See http://en.wikipedia.org/wiki/C0_and_C1_control_codes
  5040.       // C0 is 0x00-0x1F, C1 is 0x80-0x9F (inclusive).
  5041.       // We also include DEL (U+007F) and NBSP (U+00A0), which are not strictly
  5042.       // in C1 but border it.
  5043.       return (c <= 0x1F) || (0x7F <= c && c <= 0xA0);
  5044.     }
  5045.  
  5046.     function replaceFn(aMatch, aType, aHex) {
  5047.       // Leave control codes escaped, but unescape the rest of the characters.
  5048.       let c = parseInt(aHex, 16);
  5049.       return isControlCode(c) ? aMatch : String.fromCharCode(c);
  5050.     }
  5051.  
  5052.     let output = uneval(aString).replace(/\\(x)([0-9a-fA-F]{2})/g, replaceFn)
  5053.                  .replace(/\\(u)([0-9a-fA-F]{4})/g, replaceFn);
  5054.  
  5055.     return output;
  5056.   },
  5057.  
  5058.   /**
  5059.    * Determine if the jsterm execution result is inspectable or not.
  5060.    *
  5061.    * @param mixed aResult
  5062.    *        The evaluation result object you want to check if it is inspectable.
  5063.    * @returns boolean
  5064.    *          True if the object is inspectable or false otherwise.
  5065.    */
  5066.   isResultInspectable: function JST_isResultInspectable(aResult)
  5067.   {
  5068.     let isEnumerable = false;
  5069.  
  5070.     // Skip Iterators and Generators.
  5071.     if (isIteratorOrGenerator(aResult)) {
  5072.       return false;
  5073.     }
  5074.  
  5075.     for (let p in aResult) {
  5076.       isEnumerable = true;
  5077.       break;
  5078.     }
  5079.  
  5080.     return isEnumerable && typeof(aResult) != "string";
  5081.   },
  5082.  
  5083.   /**
  5084.    * Determine the type of the jsterm execution result.
  5085.    *
  5086.    * @param mixed aResult
  5087.    *        The evaluation result object you want to check.
  5088.    * @returns string
  5089.    *          Constructor name or type: string, number, boolean, regexp, date,
  5090.    *          function, object, null, undefined...
  5091.    */
  5092.   getResultType: function JST_getResultType(aResult)
  5093.   {
  5094.     let type = aResult === null ? "null" : typeof aResult;
  5095.     if (type == "object" && aResult.constructor && aResult.constructor.name) {
  5096.       type = aResult.constructor.name;
  5097.     }
  5098.  
  5099.     return type.toLowerCase();
  5100.   },
  5101.  
  5102.   clearOutput: function JST_clearOutput()
  5103.   {
  5104.     let hud = HUDService.getHudReferenceById(this.hudId);
  5105.     hud.cssNodes = {};
  5106.  
  5107.     let node = hud.outputNode;
  5108.     while (node.firstChild) {
  5109.       if (node.firstChild.classList &&
  5110.           node.firstChild.classList.contains("webconsole-msg-inspector")) {
  5111.         hud.pruneConsoleDirNode(node.firstChild);
  5112.       }
  5113.       else {
  5114.         hud.outputNode.removeChild(node.firstChild);
  5115.       }
  5116.     }
  5117.  
  5118.     hud.HUDBox.lastTimestamp = 0;
  5119.   },
  5120.  
  5121.   /**
  5122.    * Updates the size of the input field (command line) to fit its contents.
  5123.    *
  5124.    * @returns void
  5125.    */
  5126.   resizeInput: function JST_resizeInput()
  5127.   {
  5128.     let inputNode = this.inputNode;
  5129.  
  5130.     // Reset the height so that scrollHeight will reflect the natural height of
  5131.     // the contents of the input field.
  5132.     inputNode.style.height = "auto";
  5133.  
  5134.     // Now resize the input field to fit its contents.
  5135.     let scrollHeight = inputNode.inputField.scrollHeight;
  5136.     if (scrollHeight > 0) {
  5137.       inputNode.style.height = scrollHeight + "px";
  5138.     }
  5139.   },
  5140.  
  5141.   /**
  5142.    * Sets the value of the input field (command line), and resizes the field to
  5143.    * fit its contents. This method is preferred over setting "inputNode.value"
  5144.    * directly, because it correctly resizes the field.
  5145.    *
  5146.    * @param string aNewValue
  5147.    *        The new value to set.
  5148.    * @returns void
  5149.    */
  5150.   setInputValue: function JST_setInputValue(aNewValue)
  5151.   {
  5152.     this.inputNode.value = aNewValue;
  5153.     this.lastInputValue = aNewValue;
  5154.     this.completeNode.value = "";
  5155.     this.resizeInput();
  5156.   },
  5157.  
  5158.   /**
  5159.    * The inputNode "input" and "keyup" event handler.
  5160.    *
  5161.    * @param nsIDOMEvent aEvent
  5162.    */
  5163.   inputEventHandler: function JSTF_inputEventHandler(aEvent)
  5164.   {
  5165.     if (this.lastInputValue != this.inputNode.value) {
  5166.       this.resizeInput();
  5167.       this.complete(this.COMPLETE_HINT_ONLY);
  5168.       this.lastInputValue = this.inputNode.value;
  5169.     }
  5170.   },
  5171.  
  5172.   /**
  5173.    * The inputNode "keypress" event handler.
  5174.    *
  5175.    * @param nsIDOMEvent aEvent
  5176.    */
  5177.   keyPress: function JSTF_keyPress(aEvent)
  5178.   {
  5179.     if (aEvent.ctrlKey) {
  5180.       switch (aEvent.charCode) {
  5181.         case 97:
  5182.           // control-a
  5183.           this.inputNode.setSelectionRange(0, 0);
  5184.           aEvent.preventDefault();
  5185.           break;
  5186.         case 101:
  5187.           // control-e
  5188.           this.inputNode.setSelectionRange(this.inputNode.value.length,
  5189.                                            this.inputNode.value.length);
  5190.           aEvent.preventDefault();
  5191.           break;
  5192.         default:
  5193.           break;
  5194.       }
  5195.       return;
  5196.     }
  5197.     else if (aEvent.shiftKey &&
  5198.         aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
  5199.       // shift return
  5200.       // TODO: expand the inputNode height by one line
  5201.       return;
  5202.     }
  5203.  
  5204.     let inputUpdated = false;
  5205.  
  5206.     switch(aEvent.keyCode) {
  5207.       case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE:
  5208.         if (this.autocompletePopup.isOpen) {
  5209.           this.clearCompletion();
  5210.           aEvent.preventDefault();
  5211.         }
  5212.         break;
  5213.  
  5214.       case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
  5215.         if (this.autocompletePopup.isOpen) {
  5216.           this.acceptProposedCompletion();
  5217.         }
  5218.         else {
  5219.           this.execute();
  5220.         }
  5221.         aEvent.preventDefault();
  5222.         break;
  5223.  
  5224.       case Ci.nsIDOMKeyEvent.DOM_VK_UP:
  5225.         if (this.autocompletePopup.isOpen) {
  5226.           inputUpdated = this.complete(this.COMPLETE_BACKWARD);
  5227.         }
  5228.         else if (this.canCaretGoPrevious()) {
  5229.           inputUpdated = this.historyPeruse(HISTORY_BACK);
  5230.         }
  5231.         if (inputUpdated) {
  5232.           aEvent.preventDefault();
  5233.         }
  5234.         break;
  5235.  
  5236.       case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
  5237.         if (this.autocompletePopup.isOpen) {
  5238.           inputUpdated = this.complete(this.COMPLETE_FORWARD);
  5239.         }
  5240.         else if (this.canCaretGoNext()) {
  5241.           inputUpdated = this.historyPeruse(HISTORY_FORWARD);
  5242.         }
  5243.         if (inputUpdated) {
  5244.           aEvent.preventDefault();
  5245.         }
  5246.         break;
  5247.  
  5248.       case Ci.nsIDOMKeyEvent.DOM_VK_TAB:
  5249.         // Generate a completion and accept the first proposed value.
  5250.         if (this.complete(this.COMPLETE_HINT_ONLY) &&
  5251.             this.lastCompletion &&
  5252.             this.acceptProposedCompletion()) {
  5253.           aEvent.preventDefault();
  5254.         }
  5255.         break;
  5256.  
  5257.       default:
  5258.         break;
  5259.     }
  5260.   },
  5261.  
  5262.   /**
  5263.    * Go up/down the history stack of input values.
  5264.    *
  5265.    * @param number aDirection
  5266.    *        History navigation direction: HISTORY_BACK or HISTORY_FORWARD.
  5267.    *
  5268.    * @returns boolean
  5269.    *          True if the input value changed, false otherwise.
  5270.    */
  5271.   historyPeruse: function JST_historyPeruse(aDirection)
  5272.   {
  5273.     if (!this.history.length) {
  5274.       return false;
  5275.     }
  5276.  
  5277.     // Up Arrow key
  5278.     if (aDirection == HISTORY_BACK) {
  5279.       if (this.historyPlaceHolder <= 0) {
  5280.         return false;
  5281.       }
  5282.  
  5283.       let inputVal = this.history[--this.historyPlaceHolder];
  5284.       if (inputVal){
  5285.         this.setInputValue(inputVal);
  5286.       }
  5287.     }
  5288.     // Down Arrow key
  5289.     else if (aDirection == HISTORY_FORWARD) {
  5290.       if (this.historyPlaceHolder == this.history.length - 1) {
  5291.         this.historyPlaceHolder ++;
  5292.         this.setInputValue("");
  5293.       }
  5294.       else if (this.historyPlaceHolder >= (this.history.length)) {
  5295.         return false;
  5296.       }
  5297.       else {
  5298.         let inputVal = this.history[++this.historyPlaceHolder];
  5299.         if (inputVal){
  5300.           this.setInputValue(inputVal);
  5301.         }
  5302.       }
  5303.     }
  5304.     else {
  5305.       throw new Error("Invalid argument 0");
  5306.     }
  5307.  
  5308.     return true;
  5309.   },
  5310.  
  5311.   refocus: function JSTF_refocus()
  5312.   {
  5313.     this.inputNode.blur();
  5314.     this.inputNode.focus();
  5315.   },
  5316.  
  5317.   /**
  5318.    * Check if the caret is at a location that allows selecting the previous item
  5319.    * in history when the user presses the Up arrow key.
  5320.    *
  5321.    * @return boolean
  5322.    *         True if the caret is at a location that allows selecting the
  5323.    *         previous item in history when the user presses the Up arrow key,
  5324.    *         otherwise false.
  5325.    */
  5326.   canCaretGoPrevious: function JST_canCaretGoPrevious()
  5327.   {
  5328.     let node = this.inputNode;
  5329.     if (node.selectionStart != node.selectionEnd) {
  5330.       return false;
  5331.     }
  5332.  
  5333.     let multiline = /[\r\n]/.test(node.value);
  5334.     return node.selectionStart == 0 ? true :
  5335.            node.selectionStart == node.value.length && !multiline;
  5336.   },
  5337.  
  5338.   /**
  5339.    * Check if the caret is at a location that allows selecting the next item in
  5340.    * history when the user presses the Down arrow key.
  5341.    *
  5342.    * @return boolean
  5343.    *         True if the caret is at a location that allows selecting the next
  5344.    *         item in history when the user presses the Down arrow key, otherwise
  5345.    *         false.
  5346.    */
  5347.   canCaretGoNext: function JST_canCaretGoNext()
  5348.   {
  5349.     let node = this.inputNode;
  5350.     if (node.selectionStart != node.selectionEnd) {
  5351.       return false;
  5352.     }
  5353.  
  5354.     let multiline = /[\r\n]/.test(node.value);
  5355.     return node.selectionStart == node.value.length ? true :
  5356.            node.selectionStart == 0 && !multiline;
  5357.   },
  5358.  
  5359.   history: [],
  5360.  
  5361.   // Stores the data for the last completion.
  5362.   lastCompletion: null,
  5363.  
  5364.   /**
  5365.    * Completes the current typed text in the inputNode. Completion is performed
  5366.    * only if the selection/cursor is at the end of the string. If no completion
  5367.    * is found, the current inputNode value and cursor/selection stay.
  5368.    *
  5369.    * @param int type possible values are
  5370.    *    - this.COMPLETE_FORWARD: If there is more than one possible completion
  5371.    *          and the input value stayed the same compared to the last time this
  5372.    *          function was called, then the next completion of all possible
  5373.    *          completions is used. If the value changed, then the first possible
  5374.    *          completion is used and the selection is set from the current
  5375.    *          cursor position to the end of the completed text.
  5376.    *          If there is only one possible completion, then this completion
  5377.    *          value is used and the cursor is put at the end of the completion.
  5378.    *    - this.COMPLETE_BACKWARD: Same as this.COMPLETE_FORWARD but if the
  5379.    *          value stayed the same as the last time the function was called,
  5380.    *          then the previous completion of all possible completions is used.
  5381.    *    - this.COMPLETE_HINT_ONLY: If there is more than one possible
  5382.    *          completion and the input value stayed the same compared to the
  5383.    *          last time this function was called, then the same completion is
  5384.    *          used again. If there is only one possible completion, then
  5385.    *          the inputNode.value is set to this value and the selection is set
  5386.    *          from the current cursor position to the end of the completed text.
  5387.    *
  5388.    * @returns boolean true if there existed a completion for the current input,
  5389.    *          or false otherwise.
  5390.    */
  5391.   complete: function JSTF_complete(type)
  5392.   {
  5393.     let inputNode = this.inputNode;
  5394.     let inputValue = inputNode.value;
  5395.     // If the inputNode has no value, then don't try to complete on it.
  5396.     if (!inputValue) {
  5397.       this.clearCompletion();
  5398.       return false;
  5399.     }
  5400.  
  5401.     // Only complete if the selection is empty and at the end of the input.
  5402.     if (inputNode.selectionStart == inputNode.selectionEnd &&
  5403.         inputNode.selectionEnd != inputValue.length) {
  5404.       this.clearCompletion();
  5405.       return false;
  5406.     }
  5407.  
  5408.     let popup = this.autocompletePopup;
  5409.  
  5410.     if (!this.lastCompletion || this.lastCompletion.value != inputValue) {
  5411.       let properties = this.propertyProvider(this.sandbox.window, inputValue);
  5412.       if (!properties || !properties.matches.length) {
  5413.         this.clearCompletion();
  5414.         return false;
  5415.       }
  5416.  
  5417.       let items = properties.matches.map(function(aMatch) {
  5418.         return {label: aMatch};
  5419.       });
  5420.       popup.setItems(items);
  5421.       this.lastCompletion = {value: inputValue,
  5422.                              matchProp: properties.matchProp};
  5423.  
  5424.       if (items.length > 1 && !popup.isOpen) {
  5425.         popup.openPopup(this.inputNode);
  5426.       }
  5427.       else if (items.length < 2 && popup.isOpen) {
  5428.         popup.hidePopup();
  5429.       }
  5430.  
  5431.       if (items.length > 0) {
  5432.         popup.selectedIndex = 0;
  5433.         if (items.length == 1) {
  5434.           // onSelect is not fired when the popup is not open.
  5435.           this.onAutocompleteSelect();
  5436.         }
  5437.       }
  5438.     }
  5439.  
  5440.     let accepted = false;
  5441.  
  5442.     if (type != this.COMPLETE_HINT_ONLY && popup.itemCount == 1) {
  5443.       this.acceptProposedCompletion();
  5444.       accepted = true;
  5445.     }
  5446.     else if (type == this.COMPLETE_BACKWARD) {
  5447.       this.autocompletePopup.selectPreviousItem();
  5448.     }
  5449.     else if (type == this.COMPLETE_FORWARD) {
  5450.       this.autocompletePopup.selectNextItem();
  5451.     }
  5452.  
  5453.     return accepted || popup.itemCount > 0;
  5454.   },
  5455.  
  5456.   onAutocompleteSelect: function JSTF_onAutocompleteSelect()
  5457.   {
  5458.     let currentItem = this.autocompletePopup.selectedItem;
  5459.     if (currentItem && this.lastCompletion) {
  5460.       let suffix = currentItem.label.substring(this.lastCompletion.
  5461.                                                matchProp.length);
  5462.       this.updateCompleteNode(suffix);
  5463.     }
  5464.     else {
  5465.       this.updateCompleteNode("");
  5466.     }
  5467.   },
  5468.  
  5469.   /**
  5470.    * Clear the current completion information and close the autocomplete popup,
  5471.    * if needed.
  5472.    */
  5473.   clearCompletion: function JSTF_clearCompletion()
  5474.   {
  5475.     this.autocompletePopup.clearItems();
  5476.     this.lastCompletion = null;
  5477.     this.updateCompleteNode("");
  5478.     if (this.autocompletePopup.isOpen) {
  5479.       this.autocompletePopup.hidePopup();
  5480.     }
  5481.   },
  5482.  
  5483.   /**
  5484.    * Accept the proposed input completion.
  5485.    *
  5486.    * @return boolean
  5487.    *         True if there was a selected completion item and the input value
  5488.    *         was updated, false otherwise.
  5489.    */
  5490.   acceptProposedCompletion: function JSTF_acceptProposedCompletion()
  5491.   {
  5492.     let updated = false;
  5493.  
  5494.     let currentItem = this.autocompletePopup.selectedItem;
  5495.     if (currentItem && this.lastCompletion) {
  5496.       let suffix = currentItem.label.substring(this.lastCompletion.
  5497.                                                matchProp.length);
  5498.       this.setInputValue(this.inputNode.value + suffix);
  5499.       updated = true;
  5500.     }
  5501.  
  5502.     this.clearCompletion();
  5503.  
  5504.     return updated;
  5505.   },
  5506.  
  5507.   /**
  5508.    * Update the node that displays the currently selected autocomplete proposal.
  5509.    *
  5510.    * @param string aSuffix
  5511.    *        The proposed suffix for the inputNode value.
  5512.    */
  5513.   updateCompleteNode: function JSTF_updateCompleteNode(aSuffix)
  5514.   {
  5515.     // completion prefix = input, with non-control chars replaced by spaces
  5516.     let prefix = aSuffix ? this.inputNode.value.replace(/[\S]/g, " ") : "";
  5517.     this.completeNode.value = prefix + aSuffix;
  5518.   },
  5519.  
  5520.   /**
  5521.    * Destroy the JSTerm object. Call this method to avoid memory leaks.
  5522.    */
  5523.   destroy: function JST_destroy()
  5524.   {
  5525.     this.inputNode.removeEventListener("keypress", this._keyPress, false);
  5526.     this.inputNode.removeEventListener("input", this._inputEventHandler, false);
  5527.     this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
  5528.   },
  5529. };
  5530.  
  5531. /**
  5532.  * Generates and attaches the JS Terminal part of the Web Console, which
  5533.  * essentially consists of the interactive JavaScript input facility.
  5534.  *
  5535.  * @param nsWeakPtr<nsIDOMWindow> aContext
  5536.  *        A weak pointer to the DOM window that contains the Web Console.
  5537.  * @param nsIDOMNode aParentNode
  5538.  *        The Web Console wrapper node.
  5539.  * @param nsIDOMNode aExistingConsole
  5540.  *        The Web Console output node.
  5541.  * @return void
  5542.  */
  5543. function
  5544. JSTermFirefoxMixin(aContext,
  5545.                    aParentNode,
  5546.                    aExistingConsole)
  5547. {
  5548.   // aExisting Console is the existing outputNode to use in favor of
  5549.   // creating a new outputNode - this is so we can just attach the inputNode to
  5550.   // a normal HeadsUpDisplay console output, and re-use code.
  5551.   this.context = aContext;
  5552.   this.parentNode = aParentNode;
  5553.   this.existingConsoleNode = aExistingConsole;
  5554.   this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
  5555.  
  5556.   if (aParentNode.ownerDocument) {
  5557.     this.xulElementFactory =
  5558.       NodeFactory("xul", "xul", aParentNode.ownerDocument);
  5559.  
  5560.     this.textFactory = NodeFactory("text", "xul", aParentNode.ownerDocument);
  5561.     this.generateUI();
  5562.     this.attachUI();
  5563.   }
  5564.   else {
  5565.     throw new Error("aParentNode should be a DOM node with an ownerDocument property ");
  5566.   }
  5567. }
  5568.  
  5569. JSTermFirefoxMixin.prototype = {
  5570.   /**
  5571.    * Generates and attaches the UI for an entire JS Workspace or
  5572.    * just the input node used under the console output
  5573.    *
  5574.    * @returns void
  5575.    */
  5576.   generateUI: function JSTF_generateUI()
  5577.   {
  5578.     this.completeNode = this.xulElementFactory("textbox");
  5579.     this.completeNode.setAttribute("class", "jsterm-complete-node");
  5580.     this.completeNode.setAttribute("multiline", "true");
  5581.     this.completeNode.setAttribute("rows", "1");
  5582.     this.completeNode.setAttribute("tabindex", "-1");
  5583.  
  5584.     this.inputNode = this.xulElementFactory("textbox");
  5585.     this.inputNode.setAttribute("class", "jsterm-input-node");
  5586.     this.inputNode.setAttribute("multiline", "true");
  5587.     this.inputNode.setAttribute("rows", "1");
  5588.  
  5589.     let inputStack = this.xulElementFactory("stack");
  5590.     inputStack.setAttribute("class", "jsterm-stack-node");
  5591.     inputStack.setAttribute("flex", "1");
  5592.     inputStack.appendChild(this.completeNode);
  5593.     inputStack.appendChild(this.inputNode);
  5594.  
  5595.     if (this.existingConsoleNode == undefined) {
  5596.       throw new Error("This can't happen");
  5597.     }
  5598.  
  5599.     this.outputNode = this.existingConsoleNode;
  5600.  
  5601.     this.term = this.xulElementFactory("hbox");
  5602.     this.term.setAttribute("class", "jsterm-input-container");
  5603.     this.term.setAttribute("style", "direction: ltr;");
  5604.     this.term.appendChild(inputStack);
  5605.   },
  5606.  
  5607.   get inputValue()
  5608.   {
  5609.     return this.inputNode.value;
  5610.   },
  5611.  
  5612.   attachUI: function JSTF_attachUI()
  5613.   {
  5614.     this.parentNode.appendChild(this.term);
  5615.   }
  5616. };
  5617.  
  5618. /**
  5619.  * Firefox-specific Application Hooks.
  5620.  * Each Gecko-based application will need an object like this in
  5621.  * order to use the Heads Up Display
  5622.  */
  5623. function FirefoxApplicationHooks()
  5624. { }
  5625.  
  5626. FirefoxApplicationHooks.prototype = {
  5627.   /**
  5628.    * gets the current contentWindow (Firefox-specific)
  5629.    *
  5630.    * @returns nsIDOMWindow
  5631.    */
  5632.   getCurrentContext: function FAH_getCurrentContext()
  5633.   {
  5634.     return Services.wm.getMostRecentWindow("navigator:browser");
  5635.   },
  5636. };
  5637.  
  5638. //////////////////////////////////////////////////////////////////////////////
  5639. // Utility functions used by multiple callers
  5640. //////////////////////////////////////////////////////////////////////////////
  5641.  
  5642. /**
  5643.  * ConsoleUtils: a collection of globally used functions
  5644.  *
  5645.  */
  5646.  
  5647. ConsoleUtils = {
  5648.   supString: function ConsoleUtils_supString(aString)
  5649.   {
  5650.     let str = Cc["@mozilla.org/supports-string;1"].
  5651.       createInstance(Ci.nsISupportsString);
  5652.     str.data = aString;
  5653.     return str;
  5654.   },
  5655.  
  5656.   /**
  5657.    * Generates a millisecond resolution timestamp.
  5658.    *
  5659.    * @returns integer
  5660.    */
  5661.   timestamp: function ConsoleUtils_timestamp()
  5662.   {
  5663.     return Date.now();
  5664.   },
  5665.  
  5666.   /**
  5667.    * Generates a formatted timestamp string for displaying in console messages.
  5668.    *
  5669.    * @param integer [ms] Optional, allows you to specify the timestamp in
  5670.    * milliseconds since the UNIX epoch.
  5671.    * @returns string The timestamp formatted for display.
  5672.    */
  5673.   timestampString: function ConsoleUtils_timestampString(ms)
  5674.   {
  5675.     var d = new Date(ms ? ms : null);
  5676.     let hours = d.getHours(), minutes = d.getMinutes();
  5677.     let seconds = d.getSeconds(), milliseconds = d.getMilliseconds();
  5678.     let parameters = [ hours, minutes, seconds, milliseconds ];
  5679.     return HUDService.getFormatStr("timestampFormat", parameters);
  5680.   },
  5681.  
  5682.   /**
  5683.    * Scrolls a node so that it's visible in its containing XUL "scrollbox"
  5684.    * element.
  5685.    *
  5686.    * @param nsIDOMNode aNode
  5687.    *        The node to make visible.
  5688.    * @returns void
  5689.    */
  5690.   scrollToVisible: function ConsoleUtils_scrollToVisible(aNode) {
  5691.     // Find the enclosing richlistbox node.
  5692.     let richListBoxNode = aNode.parentNode;
  5693.     while (richListBoxNode.tagName != "richlistbox") {
  5694.       richListBoxNode = richListBoxNode.parentNode;
  5695.     }
  5696.  
  5697.     // Use the scroll box object interface to ensure the element is visible.
  5698.     let boxObject = richListBoxNode.scrollBoxObject;
  5699.     let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
  5700.     nsIScrollBoxObject.ensureElementIsVisible(aNode);
  5701.   },
  5702.  
  5703.   /**
  5704.    * Given a category and message body, creates a DOM node to represent an
  5705.    * incoming message. The timestamp is automatically added.
  5706.    *
  5707.    * @param nsIDOMDocument aDocument
  5708.    *        The document in which to create the node.
  5709.    * @param number aCategory
  5710.    *        The category of the message: one of the CATEGORY_* constants.
  5711.    * @param number aSeverity
  5712.    *        The severity of the message: one of the SEVERITY_* constants;
  5713.    * @param string|nsIDOMNode aBody
  5714.    *        The body of the message, either a simple string or a DOM node.
  5715.    * @param number aHUDId
  5716.    *        The HeadsUpDisplay ID.
  5717.    * @param string aSourceURL [optional]
  5718.    *        The URL of the source file that emitted the error.
  5719.    * @param number aSourceLine [optional]
  5720.    *        The line number on which the error occurred. If zero or omitted,
  5721.    *        there is no line number associated with this message.
  5722.    * @param string aClipboardText [optional]
  5723.    *        The text that should be copied to the clipboard when this node is
  5724.    *        copied. If omitted, defaults to the body text. If `aBody` is not
  5725.    *        a string, then the clipboard text must be supplied.
  5726.    * @param number aLevel [optional]
  5727.    *        The level of the console API message.
  5728.    * @return nsIDOMNode
  5729.    *         The message node: a XUL richlistitem ready to be inserted into
  5730.    *         the Web Console output node.
  5731.    */
  5732.   createMessageNode:
  5733.   function ConsoleUtils_createMessageNode(aDocument, aCategory, aSeverity,
  5734.                                           aBody, aHUDId, aSourceURL,
  5735.                                           aSourceLine, aClipboardText, aLevel) {
  5736.     if (typeof aBody != "string" && aClipboardText == null && aBody.innerText) {
  5737.       aClipboardText = aBody.innerText;
  5738.     }
  5739.  
  5740.     // Make the icon container, which is a vertical box. Its purpose is to
  5741.     // ensure that the icon stays anchored at the top of the message even for
  5742.     // long multi-line messages.
  5743.     let iconContainer = aDocument.createElementNS(XUL_NS, "vbox");
  5744.     iconContainer.classList.add("webconsole-msg-icon-container");
  5745.     // Apply the curent group by indenting appropriately.
  5746.     let hud = HUDService.getHudReferenceById(aHUDId);
  5747.     iconContainer.style.marginLeft = hud.groupDepth * GROUP_INDENT + "px";
  5748.  
  5749.     // Make the icon node. It's sprited and the actual region of the image is
  5750.     // determined by CSS rules.
  5751.     let iconNode = aDocument.createElementNS(XUL_NS, "image");
  5752.     iconNode.classList.add("webconsole-msg-icon");
  5753.     iconContainer.appendChild(iconNode);
  5754.  
  5755.     // Make the spacer that positions the icon.
  5756.     let spacer = aDocument.createElementNS(XUL_NS, "spacer");
  5757.     spacer.setAttribute("flex", "1");
  5758.     iconContainer.appendChild(spacer);
  5759.  
  5760.     // Create the message body, which contains the actual text of the message.
  5761.     let bodyNode = aDocument.createElementNS(XUL_NS, "description");
  5762.     bodyNode.setAttribute("flex", "1");
  5763.     bodyNode.classList.add("webconsole-msg-body");
  5764.  
  5765.     // Store the body text, since it is needed later for the property tree
  5766.     // case.
  5767.     let body = aBody;
  5768.     // If a string was supplied for the body, turn it into a DOM node and an
  5769.     // associated clipboard string now.
  5770.     aClipboardText = aClipboardText ||
  5771.                      (aBody + (aSourceURL ? " @ " + aSourceURL : "") +
  5772.                               (aSourceLine ? ":" + aSourceLine : ""));
  5773.     aBody = aBody instanceof Ci.nsIDOMNode && !(aLevel == "dir") ?
  5774.             aBody : aDocument.createTextNode(aBody);
  5775.  
  5776.     if (!aBody.nodeType) {
  5777.       aBody = aDocument.createTextNode(aBody.toString());
  5778.     }
  5779.     if (typeof aBody == "string") {
  5780.       aBody = aDocument.createTextNode(aBody);
  5781.     }
  5782.  
  5783.     bodyNode.appendChild(aBody);
  5784.  
  5785.     let repeatContainer = aDocument.createElementNS(XUL_NS, "hbox");
  5786.     repeatContainer.setAttribute("align", "start");
  5787.     let repeatNode = aDocument.createElementNS(XUL_NS, "label");
  5788.     repeatNode.setAttribute("value", "1");
  5789.     repeatNode.classList.add("webconsole-msg-repeat");
  5790.     repeatContainer.appendChild(repeatNode);
  5791.  
  5792.     // Create the timestamp.
  5793.     let timestampNode = aDocument.createElementNS(XUL_NS, "label");
  5794.     timestampNode.classList.add("webconsole-timestamp");
  5795.     let timestamp = ConsoleUtils.timestamp();
  5796.     let timestampString = ConsoleUtils.timestampString(timestamp);
  5797.     timestampNode.setAttribute("value", timestampString);
  5798.  
  5799.     // Create the source location (e.g. www.example.com:6) that sits on the
  5800.     // right side of the message, if applicable.
  5801.     let locationNode;
  5802.     if (aSourceURL) {
  5803.       locationNode = this.createLocationNode(aDocument, aSourceURL,
  5804.                                              aSourceLine);
  5805.     }
  5806.  
  5807.     // Create the containing node and append all its elements to it.
  5808.     let node = aDocument.createElementNS(XUL_NS, "richlistitem");
  5809.     node.clipboardText = aClipboardText;
  5810.     node.classList.add("hud-msg-node");
  5811.  
  5812.     node.timestamp = timestamp;
  5813.     ConsoleUtils.setMessageType(node, aCategory, aSeverity);
  5814.  
  5815.     node.appendChild(timestampNode);
  5816.     node.appendChild(iconContainer);
  5817.     // Display the object tree after the message node.
  5818.     if (aLevel == "dir") {
  5819.       // Make the body container, which is a vertical box, for grouping the text
  5820.       // and tree widgets.
  5821.       let bodyContainer = aDocument.createElement("vbox");
  5822.       bodyContainer.setAttribute("flex", "1");
  5823.       bodyContainer.appendChild(bodyNode);
  5824.       // Create the tree.
  5825.       let tree = createElement(aDocument, "tree", {
  5826.         flex: 1,
  5827.         hidecolumnpicker: "true"
  5828.       });
  5829.  
  5830.       let treecols = aDocument.createElement("treecols");
  5831.       let treecol = createElement(aDocument, "treecol", {
  5832.         primary: "true",
  5833.         flex: 1,
  5834.         hideheader: "true",
  5835.         ignoreincolumnpicker: "true"
  5836.       });
  5837.       treecols.appendChild(treecol);
  5838.       tree.appendChild(treecols);
  5839.  
  5840.       tree.appendChild(aDocument.createElement("treechildren"));
  5841.  
  5842.       bodyContainer.appendChild(tree);
  5843.       node.appendChild(bodyContainer);
  5844.       node.classList.add("webconsole-msg-inspector");
  5845.       // Create the treeView object.
  5846.       let treeView = node.propertyTreeView = new PropertyTreeView();
  5847.       treeView.data = body;
  5848.       tree.setAttribute("rows", treeView.rowCount);
  5849.     }
  5850.     else {
  5851.       node.appendChild(bodyNode);
  5852.     }
  5853.     node.appendChild(repeatContainer);
  5854.     if (locationNode) {
  5855.       node.appendChild(locationNode);
  5856.     }
  5857.  
  5858.     node.setAttribute("id", "console-msg-" + HUDService.sequenceId());
  5859.  
  5860.     return node;
  5861.   },
  5862.  
  5863.   /**
  5864.    * Adjusts the category and severity of the given message, clearing the old
  5865.    * category and severity if present.
  5866.    *
  5867.    * @param nsIDOMNode aMessageNode
  5868.    *        The message node to alter.
  5869.    * @param number aNewCategory
  5870.    *        The new category for the message; one of the CATEGORY_ constants.
  5871.    * @param number aNewSeverity
  5872.    *        The new severity for the message; one of the SEVERITY_ constants.
  5873.    * @return void
  5874.    */
  5875.   setMessageType:
  5876.   function ConsoleUtils_setMessageType(aMessageNode, aNewCategory,
  5877.                                        aNewSeverity) {
  5878.     // Remove the old CSS classes, if applicable.
  5879.     if ("category" in aMessageNode) {
  5880.       let oldCategory = aMessageNode.category;
  5881.       let oldSeverity = aMessageNode.severity;
  5882.       aMessageNode.classList.remove("webconsole-msg-" +
  5883.                                     CATEGORY_CLASS_FRAGMENTS[oldCategory]);
  5884.       aMessageNode.classList.remove("webconsole-msg-" +
  5885.                                     SEVERITY_CLASS_FRAGMENTS[oldSeverity]);
  5886.       let key = "hud-" + MESSAGE_PREFERENCE_KEYS[oldCategory][oldSeverity];
  5887.       aMessageNode.classList.remove(key);
  5888.     }
  5889.  
  5890.     // Add in the new CSS classes.
  5891.     aMessageNode.category = aNewCategory;
  5892.     aMessageNode.severity = aNewSeverity;
  5893.     aMessageNode.classList.add("webconsole-msg-" +
  5894.                                CATEGORY_CLASS_FRAGMENTS[aNewCategory]);
  5895.     aMessageNode.classList.add("webconsole-msg-" +
  5896.                                SEVERITY_CLASS_FRAGMENTS[aNewSeverity]);
  5897.     let key = "hud-" + MESSAGE_PREFERENCE_KEYS[aNewCategory][aNewSeverity];
  5898.     aMessageNode.classList.add(key);
  5899.   },
  5900.  
  5901.   /**
  5902.    * Creates the XUL label that displays the textual location of an incoming
  5903.    * message.
  5904.    *
  5905.    * @param nsIDOMDocument aDocument
  5906.    *        The document in which to create the node.
  5907.    * @param string aSourceURL
  5908.    *        The URL of the source file responsible for the error.
  5909.    * @param number aSourceLine [optional]
  5910.    *        The line number on which the error occurred. If zero or omitted,
  5911.    *        there is no line number associated with this message.
  5912.    * @return nsIDOMNode
  5913.    *         The new XUL label node, ready to be added to the message node.
  5914.    */
  5915.   createLocationNode:
  5916.   function ConsoleUtils_createLocationNode(aDocument, aSourceURL,
  5917.                                            aSourceLine) {
  5918.     let locationNode = aDocument.createElementNS(XUL_NS, "label");
  5919.  
  5920.     // Create the text, which consists of an abbreviated version of the URL
  5921.     // plus an optional line number.
  5922.     let text = ConsoleUtils.abbreviateSourceURL(aSourceURL);
  5923.     if (aSourceLine) {
  5924.       text += ":" + aSourceLine;
  5925.     }
  5926.     locationNode.setAttribute("value", text);
  5927.  
  5928.     // Style appropriately.
  5929.     locationNode.setAttribute("crop", "center");
  5930.     locationNode.setAttribute("title", aSourceURL);
  5931.     locationNode.classList.add("webconsole-location");
  5932.     locationNode.classList.add("text-link");
  5933.  
  5934.     // Make the location clickable.
  5935.     locationNode.addEventListener("click", function() {
  5936.       if (aSourceURL == "Scratchpad") {
  5937.         let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
  5938.         if (win) {
  5939.           win.focus();
  5940.         }
  5941.         return;
  5942.       }
  5943.       let viewSourceUtils = aDocument.defaultView.gViewSourceUtils;
  5944.       viewSourceUtils.viewSource(aSourceURL, null, aDocument, aSourceLine);
  5945.     }, true);
  5946.  
  5947.     return locationNode;
  5948.   },
  5949.  
  5950.   /**
  5951.    * Applies the user's filters to a newly-created message node via CSS
  5952.    * classes.
  5953.    *
  5954.    * @param nsIDOMNode aNode
  5955.    *        The newly-created message node.
  5956.    * @param string aHUDId
  5957.    *        The ID of the HUD which this node is to be inserted into.
  5958.    */
  5959.   filterMessageNode: function ConsoleUtils_filterMessageNode(aNode, aHUDId) {
  5960.     // Filter by the message type.
  5961.     let prefKey = MESSAGE_PREFERENCE_KEYS[aNode.category][aNode.severity];
  5962.     if (prefKey && !HUDService.getFilterState(aHUDId, prefKey)) {
  5963.       // The node is filtered by type.
  5964.       aNode.classList.add("hud-filtered-by-type");
  5965.     }
  5966.  
  5967.     // Filter on the search string.
  5968.     let search = HUDService.getFilterStringByHUDId(aHUDId);
  5969.     let text = aNode.clipboardText;
  5970.  
  5971.     // if string matches the filter text
  5972.     if (!HUDService.stringMatchesFilters(text, search)) {
  5973.       aNode.classList.add("hud-filtered-by-string");
  5974.     }
  5975.   },
  5976.  
  5977.   /**
  5978.    * Merge the attributes of the two nodes that are about to be filtered.
  5979.    * Increment the number of repeats of aOriginal.
  5980.    *
  5981.    * @param nsIDOMNode aOriginal
  5982.    *        The Original Node. The one being merged into.
  5983.    * @param nsIDOMNode aFiltered
  5984.    *        The node being filtered out because it is repeated.
  5985.    */
  5986.   mergeFilteredMessageNode:
  5987.   function ConsoleUtils_mergeFilteredMessageNode(aOriginal, aFiltered) {
  5988.     // childNodes[3].firstChild is the node containing the number of repetitions
  5989.     // of a node.
  5990.     let repeatNode = aOriginal.childNodes[3].firstChild;
  5991.     if (!repeatNode) {
  5992.       return aOriginal; // no repeat node, return early.
  5993.     }
  5994.  
  5995.     let occurrences = parseInt(repeatNode.getAttribute("value")) + 1;
  5996.     repeatNode.setAttribute("value", occurrences);
  5997.   },
  5998.  
  5999.   /**
  6000.    * Filter the css node from the output node if it is a repeat. CSS messages
  6001.    * are merged with previous messages if they occurred in the past.
  6002.    *
  6003.    * @param nsIDOMNode aNode
  6004.    *        The message node to be filtered or not.
  6005.    * @param nsIDOMNode aOutput
  6006.    *        The outputNode of the HUD.
  6007.    * @returns boolean
  6008.    *         true if the message is filtered, false otherwise.
  6009.    */
  6010.   filterRepeatedCSS:
  6011.   function ConsoleUtils_filterRepeatedCSS(aNode, aOutput, aHUDId) {
  6012.     let hud = HUDService.getHudReferenceById(aHUDId);
  6013.  
  6014.     // childNodes[2] is the description node containing the text of the message.
  6015.     let description = aNode.childNodes[2].textContent;
  6016.     let location;
  6017.  
  6018.     // childNodes[4] represents the location (source URL) of the error message.
  6019.     // The full source URL is stored in the title attribute.
  6020.     if (aNode.childNodes[4]) {
  6021.       // browser_webconsole_bug_595934_message_categories.js
  6022.       location = aNode.childNodes[4].getAttribute("title");
  6023.     }
  6024.     else {
  6025.       location = "";
  6026.     }
  6027.  
  6028.     let dupe = hud.cssNodes[description + location];
  6029.     if (!dupe) {
  6030.       // no matching nodes
  6031.       hud.cssNodes[description + location] = aNode;
  6032.       return false;
  6033.     }
  6034.  
  6035.     this.mergeFilteredMessageNode(dupe, aNode);
  6036.  
  6037.     return true;
  6038.   },
  6039.  
  6040.   /**
  6041.    * Filter the console node from the output node if it is a repeat. Console
  6042.    * messages are filtered from the output if and only if they match the
  6043.    * immediately preceding message. The output node's last occurrence should
  6044.    * have its timestamp updated.
  6045.    *
  6046.    * @param nsIDOMNode aNode
  6047.    *        The message node to be filtered or not.
  6048.    * @param nsIDOMNode aOutput
  6049.    *        The outputNode of the HUD.
  6050.    * @return boolean
  6051.    *         true if the message is filtered, false otherwise.
  6052.    */
  6053.   filterRepeatedConsole:
  6054.   function ConsoleUtils_filterRepeatedConsole(aNode, aOutput) {
  6055.     let lastMessage = aOutput.lastChild;
  6056.  
  6057.     // childNodes[2] is the description element
  6058.     if (lastMessage && !aNode.classList.contains("webconsole-msg-inspector") &&
  6059.         aNode.childNodes[2].textContent ==
  6060.         lastMessage.childNodes[2].textContent) {
  6061.       this.mergeFilteredMessageNode(lastMessage, aNode);
  6062.       return true;
  6063.     }
  6064.  
  6065.     return false;
  6066.   },
  6067.  
  6068.   /**
  6069.    * Filters a node appropriately, then sends it to the output, regrouping and
  6070.    * pruning output as necessary.
  6071.    *
  6072.    * @param nsIDOMNode aNode
  6073.    *        The message node to send to the output.
  6074.    * @param string aHUDId
  6075.    *        The ID of the HUD in which to insert this node.
  6076.    */
  6077.   outputMessageNode: function ConsoleUtils_outputMessageNode(aNode, aHUDId) {
  6078.     ConsoleUtils.filterMessageNode(aNode, aHUDId);
  6079.     let outputNode = HUDService.hudReferences[aHUDId].outputNode;
  6080.  
  6081.     let scrolledToBottom = ConsoleUtils.isOutputScrolledToBottom(outputNode);
  6082.  
  6083.     let isRepeated = false;
  6084.     if (aNode.classList.contains("webconsole-msg-cssparser")) {
  6085.       isRepeated = this.filterRepeatedCSS(aNode, outputNode, aHUDId);
  6086.     }
  6087.  
  6088.     if (!isRepeated &&
  6089.         (aNode.classList.contains("webconsole-msg-console") ||
  6090.          aNode.classList.contains("webconsole-msg-exception") ||
  6091.          aNode.classList.contains("webconsole-msg-error"))) {
  6092.       isRepeated = this.filterRepeatedConsole(aNode, outputNode);
  6093.     }
  6094.  
  6095.     if (!isRepeated) {
  6096.       outputNode.appendChild(aNode);
  6097.     }
  6098.  
  6099.     HUDService.regroupOutput(outputNode);
  6100.  
  6101.     if (pruneConsoleOutputIfNecessary(aHUDId, aNode.category) == 0) {
  6102.       // We can't very well scroll to make the message node visible if the log
  6103.       // limit is zero and the node was destroyed in the first place.
  6104.       return;
  6105.     }
  6106.  
  6107.     let isInputOutput = aNode.classList.contains("webconsole-msg-input") ||
  6108.                         aNode.classList.contains("webconsole-msg-output");
  6109.     let isFiltered = aNode.classList.contains("hud-filtered-by-string") ||
  6110.                      aNode.classList.contains("hud-filtered-by-type");
  6111.  
  6112.     // Scroll to the new node if it is not filtered, and if the output node is
  6113.     // scrolled at the bottom or if the new node is a jsterm input/output
  6114.     // message.
  6115.     if (!isFiltered && !isRepeated && (scrolledToBottom || isInputOutput)) {
  6116.       ConsoleUtils.scrollToVisible(aNode);
  6117.     }
  6118.  
  6119.     let id = ConsoleUtils.supString(aHUDId);
  6120.     let nodeID = aNode.getAttribute("id");
  6121.     Services.obs.notifyObservers(id, "web-console-message-created", nodeID);
  6122.   },
  6123.  
  6124.   /**
  6125.    * Check if the given output node is scrolled to the bottom.
  6126.    *
  6127.    * @param nsIDOMNode aOutputNode
  6128.    * @return boolean
  6129.    *         True if the output node is scrolled to the bottom, or false
  6130.    *         otherwise.
  6131.    */
  6132.   isOutputScrolledToBottom:
  6133.   function ConsoleUtils_isOutputScrolledToBottom(aOutputNode)
  6134.   {
  6135.     let lastNodeHeight = aOutputNode.lastChild ?
  6136.                          aOutputNode.lastChild.clientHeight : 0;
  6137.     let scrollBox = aOutputNode.scrollBoxObject.element;
  6138.  
  6139.     return scrollBox.scrollTop + scrollBox.clientHeight >=
  6140.            scrollBox.scrollHeight - lastNodeHeight / 2;
  6141.   },
  6142.  
  6143.   /**
  6144.    * Abbreviates the given source URL so that it can be displayed flush-right
  6145.    * without being too distracting.
  6146.    *
  6147.    * @param string aSourceURL
  6148.    *        The source URL to shorten.
  6149.    * @return string
  6150.    *         The abbreviated form of the source URL.
  6151.    */
  6152.   abbreviateSourceURL: function ConsoleUtils_abbreviateSourceURL(aSourceURL) {
  6153.     // Remove any query parameters.
  6154.     let hookIndex = aSourceURL.indexOf("?");
  6155.     if (hookIndex > -1) {
  6156.       aSourceURL = aSourceURL.substring(0, hookIndex);
  6157.     }
  6158.  
  6159.     // Remove a trailing "/".
  6160.     if (aSourceURL[aSourceURL.length - 1] == "/") {
  6161.       aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
  6162.     }
  6163.  
  6164.     // Remove all but the last path component.
  6165.     let slashIndex = aSourceURL.lastIndexOf("/");
  6166.     if (slashIndex > -1) {
  6167.       aSourceURL = aSourceURL.substring(slashIndex + 1);
  6168.     }
  6169.  
  6170.     return aSourceURL;
  6171.   }
  6172. };
  6173.  
  6174. //////////////////////////////////////////////////////////////////////////
  6175. // HeadsUpDisplayUICommands
  6176. //////////////////////////////////////////////////////////////////////////
  6177.  
  6178. HeadsUpDisplayUICommands = {
  6179.   toggleHUD: function UIC_toggleHUD() {
  6180.     var window = HUDService.currentContext();
  6181.     var gBrowser = window.gBrowser;
  6182.     var linkedBrowser = gBrowser.selectedTab.linkedBrowser;
  6183.     var tabId = gBrowser.getNotificationBox(linkedBrowser).getAttribute("id");
  6184.     var hudId = "hud_" + tabId;
  6185.     var ownerDocument = gBrowser.selectedTab.ownerDocument;
  6186.     var hud = ownerDocument.getElementById(hudId);
  6187.     var hudRef = HUDService.hudReferences[hudId];
  6188.  
  6189.     if (hudRef && hud) {
  6190.       if (hudRef.consolePanel) {
  6191.         hudRef.consolePanel.hidePopup();
  6192.       }
  6193.       else {
  6194.         HUDService.storeHeight(hudId);
  6195.  
  6196.         HUDService.animate(hudId, ANIMATE_OUT, function() {
  6197.           // If the user closes the console while the console is animating away,
  6198.           // then these callbacks will queue up, but all the callbacks after the
  6199.           // first will have no console to operate on. This test handles this
  6200.           // case gracefully.
  6201.           if (ownerDocument.getElementById(hudId)) {
  6202.             HUDService.deactivateHUDForContext(gBrowser.selectedTab, true);
  6203.           }
  6204.         });
  6205.       }
  6206.     }
  6207.     else {
  6208.       HUDService.activateHUDForContext(gBrowser.selectedTab, true);
  6209.       HUDService.animate(hudId, ANIMATE_IN);
  6210.     }
  6211.   },
  6212.  
  6213.   /**
  6214.    * Find the hudId for the active chrome window.
  6215.    * @return string|null
  6216.    *         The hudId or null if the active chrome window has no open Web
  6217.    *         Console.
  6218.    */
  6219.   getOpenHUD: function UIC_getOpenHUD() {
  6220.     let chromeWindow = HUDService.currentContext();
  6221.     let contentWindow = chromeWindow.gBrowser.selectedBrowser.contentWindow;
  6222.     return HUDService.getHudIdByWindow(contentWindow);
  6223.   },
  6224.  
  6225.   /**
  6226.    * The event handler that is called whenever a user switches a filter on or
  6227.    * off.
  6228.    *
  6229.    * @param nsIDOMEvent aEvent
  6230.    *        The event that triggered the filter change.
  6231.    * @return boolean
  6232.    */
  6233.   toggleFilter: function UIC_toggleFilter(aEvent) {
  6234.     let hudId = this.getAttribute("hudId");
  6235.     switch (this.tagName) {
  6236.       case "toolbarbutton": {
  6237.         let originalTarget = aEvent.originalTarget;
  6238.         let classes = originalTarget.classList;
  6239.  
  6240.         if (originalTarget.localName !== "toolbarbutton") {
  6241.           // Oddly enough, the click event is sent to the menu button when
  6242.           // selecting a menu item with the mouse. Detect this case and bail
  6243.           // out.
  6244.           break;
  6245.         }
  6246.  
  6247.         if (!classes.contains("toolbarbutton-menubutton-button") &&
  6248.             originalTarget.getAttribute("type") === "menu-button") {
  6249.           // This is a filter button with a drop-down. The user clicked the
  6250.           // drop-down, so do nothing. (The menu will automatically appear
  6251.           // without our intervention.)
  6252.           break;
  6253.         }
  6254.  
  6255.         let state = this.getAttribute("checked") !== "true";
  6256.         this.setAttribute("checked", state);
  6257.  
  6258.         // This is a filter button with a drop-down, and the user clicked the
  6259.         // main part of the button. Go through all the severities and toggle
  6260.         // their associated filters.
  6261.         let menuItems = this.querySelectorAll("menuitem");
  6262.         for (let i = 0; i < menuItems.length; i++) {
  6263.           menuItems[i].setAttribute("checked", state);
  6264.           let prefKey = menuItems[i].getAttribute("prefKey");
  6265.           HUDService.setFilterState(hudId, prefKey, state);
  6266.         }
  6267.         break;
  6268.       }
  6269.  
  6270.       case "menuitem": {
  6271.         let state = this.getAttribute("checked") !== "true";
  6272.         this.setAttribute("checked", state);
  6273.  
  6274.         let prefKey = this.getAttribute("prefKey");
  6275.         HUDService.setFilterState(hudId, prefKey, state);
  6276.  
  6277.         // Adjust the state of the button appropriately.
  6278.         let menuPopup = this.parentNode;
  6279.  
  6280.         let someChecked = false;
  6281.         let menuItem = menuPopup.firstChild;
  6282.         while (menuItem) {
  6283.           if (menuItem.getAttribute("checked") === "true") {
  6284.             someChecked = true;
  6285.             break;
  6286.           }
  6287.           menuItem = menuItem.nextSibling;
  6288.         }
  6289.         let toolbarButton = menuPopup.parentNode;
  6290.         toolbarButton.setAttribute("checked", someChecked);
  6291.         break;
  6292.       }
  6293.     }
  6294.  
  6295.     return true;
  6296.   },
  6297.  
  6298.   command: function UIC_command(aButton) {
  6299.     var filter = aButton.getAttribute("buttonType");
  6300.     var hudId = aButton.getAttribute("hudId");
  6301.     switch (filter) {
  6302.       case "copy": {
  6303.         let outputNode = HUDService.hudReferences[hudId].outputNode;
  6304.         HUDService.copySelectedItems(outputNode);
  6305.         break;
  6306.       }
  6307.       case "selectAll": {
  6308.         HUDService.hudReferences[hudId].outputNode.selectAll();
  6309.         break;
  6310.       }
  6311.       case "saveBodies": {
  6312.         let checked = aButton.getAttribute("checked") === "true";
  6313.         HUDService.saveRequestAndResponseBodies = checked;
  6314.         break;
  6315.       }
  6316.     }
  6317.   },
  6318.  
  6319. };
  6320.  
  6321. //////////////////////////////////////////////////////////////////////////
  6322. // ConsoleStorage
  6323. //////////////////////////////////////////////////////////////////////////
  6324.  
  6325. var prefs = Services.prefs;
  6326.  
  6327. const GLOBAL_STORAGE_INDEX_ID = "GLOBAL_CONSOLE";
  6328. const PREFS_BRANCH_PREF = "devtools.hud.display.filter";
  6329. const PREFS_PREFIX = "devtools.hud.display.filter.";
  6330. const PREFS = { network: PREFS_PREFIX + "network",
  6331.                 networkinfo: PREFS_PREFIX + "networkinfo",
  6332.                 csserror: PREFS_PREFIX + "csserror",
  6333.                 cssparser: PREFS_PREFIX + "cssparser",
  6334.                 exception: PREFS_PREFIX + "exception",
  6335.                 jswarn: PREFS_PREFIX + "jswarn",
  6336.                 error: PREFS_PREFIX + "error",
  6337.                 info: PREFS_PREFIX + "info",
  6338.                 warn: PREFS_PREFIX + "warn",
  6339.                 log: PREFS_PREFIX + "log",
  6340.                 global: PREFS_PREFIX + "global",
  6341.               };
  6342.  
  6343. function ConsoleStorage()
  6344. {
  6345.   this.sequencer = null;
  6346.   this.consoleDisplays = {};
  6347.   // each display will have an index that tracks each ConsoleEntry
  6348.   this.displayIndexes = {};
  6349.   this.globalStorageIndex = [];
  6350.   this.globalDisplay = {};
  6351.   this.createDisplay(GLOBAL_STORAGE_INDEX_ID);
  6352.   // TODO: need to create a method that truncates the message
  6353.   // see bug 570543
  6354.  
  6355.   // store an index of display prefs
  6356.   this.displayPrefs = {};
  6357.  
  6358.   // check prefs for existence, create & load if absent, load them if present
  6359.   let filterPrefs;
  6360.   let defaultDisplayPrefs;
  6361.  
  6362.   try {
  6363.     filterPrefs = prefs.getBoolPref(PREFS_BRANCH_PREF);
  6364.   }
  6365.   catch (ex) {
  6366.     filterPrefs = false;
  6367.   }
  6368.  
  6369.   // TODO: for FINAL release,
  6370.   // use the sitePreferencesService to save specific site prefs
  6371.   // see bug 570545
  6372.  
  6373.   if (filterPrefs) {
  6374.     defaultDisplayPrefs = {
  6375.       network: (prefs.getBoolPref(PREFS.network) ? true: false),
  6376.       networkinfo: (prefs.getBoolPref(PREFS.networkinfo) ? true: false),
  6377.       csserror: (prefs.getBoolPref(PREFS.csserror) ? true: false),
  6378.       cssparser: (prefs.getBoolPref(PREFS.cssparser) ? true: false),
  6379.       exception: (prefs.getBoolPref(PREFS.exception) ? true: false),
  6380.       jswarn: (prefs.getBoolPref(PREFS.jswarn) ? true: false),
  6381.       error: (prefs.getBoolPref(PREFS.error) ? true: false),
  6382.       info: (prefs.getBoolPref(PREFS.info) ? true: false),
  6383.       warn: (prefs.getBoolPref(PREFS.warn) ? true: false),
  6384.       log: (prefs.getBoolPref(PREFS.log) ? true: false),
  6385.       global: (prefs.getBoolPref(PREFS.global) ? true: false),
  6386.     };
  6387.   }
  6388.   else {
  6389.     prefs.setBoolPref(PREFS_BRANCH_PREF, false);
  6390.     // default prefs for each HeadsUpDisplay
  6391.     prefs.setBoolPref(PREFS.network, true);
  6392.     prefs.setBoolPref(PREFS.networkinfo, true);
  6393.     prefs.setBoolPref(PREFS.csserror, true);
  6394.     prefs.setBoolPref(PREFS.cssparser, true);
  6395.     prefs.setBoolPref(PREFS.exception, true);
  6396.     prefs.setBoolPref(PREFS.jswarn, true);
  6397.     prefs.setBoolPref(PREFS.error, true);
  6398.     prefs.setBoolPref(PREFS.info, true);
  6399.     prefs.setBoolPref(PREFS.warn, true);
  6400.     prefs.setBoolPref(PREFS.log, true);
  6401.     prefs.setBoolPref(PREFS.global, false);
  6402.  
  6403.     defaultDisplayPrefs = {
  6404.       network: prefs.getBoolPref(PREFS.network),
  6405.       networkinfo: prefs.getBoolPref(PREFS.networkinfo),
  6406.       csserror: prefs.getBoolPref(PREFS.csserror),
  6407.       cssparser: prefs.getBoolPref(PREFS.cssparser),
  6408.       exception: prefs.getBoolPref(PREFS.exception),
  6409.       jswarn: prefs.getBoolPref(PREFS.jswarn),
  6410.       error: prefs.getBoolPref(PREFS.error),
  6411.       info: prefs.getBoolPref(PREFS.info),
  6412.       warn: prefs.getBoolPref(PREFS.warn),
  6413.       log: prefs.getBoolPref(PREFS.log),
  6414.       global: prefs.getBoolPref(PREFS.global),
  6415.     };
  6416.   }
  6417.   this.defaultDisplayPrefs = defaultDisplayPrefs;
  6418. }
  6419.  
  6420. ConsoleStorage.prototype = {
  6421.  
  6422.   updateDefaultDisplayPrefs:
  6423.   function CS_updateDefaultDisplayPrefs(aPrefsObject) {
  6424.     prefs.setBoolPref(PREFS.network, (aPrefsObject.network ? true : false));
  6425.     prefs.setBoolPref(PREFS.networkinfo,
  6426.                       (aPrefsObject.networkinfo ? true : false));
  6427.     prefs.setBoolPref(PREFS.csserror, (aPrefsObject.csserror ? true : false));
  6428.     prefs.setBoolPref(PREFS.cssparser, (aPrefsObject.cssparser ? true : false));
  6429.     prefs.setBoolPref(PREFS.exception, (aPrefsObject.exception ? true : false));
  6430.     prefs.setBoolPref(PREFS.jswarn, (aPrefsObject.jswarn ? true : false));
  6431.     prefs.setBoolPref(PREFS.error, (aPrefsObject.error ? true : false));
  6432.     prefs.setBoolPref(PREFS.info, (aPrefsObject.info ? true : false));
  6433.     prefs.setBoolPref(PREFS.warn, (aPrefsObject.warn ? true : false));
  6434.     prefs.setBoolPref(PREFS.log, (aPrefsObject.log ? true : false));
  6435.     prefs.setBoolPref(PREFS.global, (aPrefsObject.global ? true : false));
  6436.   },
  6437.  
  6438.   sequenceId: function CS_sequencerId()
  6439.   {
  6440.     if (!this.sequencer) {
  6441.       this.sequencer = this.createSequencer();
  6442.     }
  6443.     return this.sequencer.next();
  6444.   },
  6445.  
  6446.   createSequencer: function CS_createSequencer()
  6447.   {
  6448.     function sequencer(aInt) {
  6449.       while(1) {
  6450.         aInt++;
  6451.         yield aInt;
  6452.       }
  6453.     }
  6454.     return sequencer(-1);
  6455.   },
  6456.  
  6457.   globalStore: function CS_globalStore(aIndex)
  6458.   {
  6459.     return this.displayStore(GLOBAL_CONSOLE_DOM_NODE_ID);
  6460.   },
  6461.  
  6462.   displayStore: function CS_displayStore(aId)
  6463.   {
  6464.     var self = this;
  6465.     var idx = -1;
  6466.     var id = aId;
  6467.     var aLength = self.displayIndexes[id].length;
  6468.  
  6469.     function displayStoreGenerator(aInt, aLength)
  6470.     {
  6471.       // create a generator object to iterate through any of the display stores
  6472.       // from any index-starting-point
  6473.       while(1) {
  6474.         // throw if we exceed the length of displayIndexes?
  6475.         aInt++;
  6476.         var indexIt = self.displayIndexes[id];
  6477.         var index = indexIt[aInt];
  6478.         if (aLength < aInt) {
  6479.           // try to see if we have more entries:
  6480.           var newLength = self.displayIndexes[id].length;
  6481.           if (newLength > aLength) {
  6482.             aLength = newLength;
  6483.           }
  6484.           else {
  6485.             throw new StopIteration();
  6486.           }
  6487.         }
  6488.         var entry = self.consoleDisplays[id][index];
  6489.         yield entry;
  6490.       }
  6491.     }
  6492.  
  6493.     return displayStoreGenerator(-1, aLength);
  6494.   },
  6495.  
  6496.   recordEntries: function CS_recordEntries(aHUDId, aConfigArray)
  6497.   {
  6498.     var len = aConfigArray.length;
  6499.     for (var i = 0; i < len; i++){
  6500.       this.recordEntry(aHUDId, aConfigArray[i]);
  6501.     }
  6502.   },
  6503.  
  6504.  
  6505.   recordEntry: function CS_recordEntry(aHUDId, aConfig)
  6506.   {
  6507.     var id = this.sequenceId();
  6508.  
  6509.     this.globalStorageIndex[id] = { hudId: aHUDId };
  6510.  
  6511.     var displayStorage = this.consoleDisplays[aHUDId];
  6512.  
  6513.     var displayIndex = this.displayIndexes[aHUDId];
  6514.  
  6515.     if (displayStorage && displayIndex) {
  6516.       var entry = new ConsoleEntry(aConfig, id);
  6517.       displayIndex.push(entry.id);
  6518.       displayStorage[entry.id] = entry;
  6519.       return entry;
  6520.     }
  6521.     else {
  6522.       throw new Error("Cannot get displayStorage or index object for id " + aHUDId);
  6523.     }
  6524.   },
  6525.  
  6526.   getEntry: function CS_getEntry(aId)
  6527.   {
  6528.     var display = this.globalStorageIndex[aId];
  6529.     var storName = display.hudId;
  6530.     return this.consoleDisplays[storName][aId];
  6531.   },
  6532.  
  6533.   updateEntry: function CS_updateEntry(aUUID)
  6534.   {
  6535.     // update an individual entry
  6536.     // TODO: see bug 568634
  6537.   },
  6538.  
  6539.   createDisplay: function CS_createdisplay(aId)
  6540.   {
  6541.     if (!this.consoleDisplays[aId]) {
  6542.       this.consoleDisplays[aId] = {};
  6543.       this.displayIndexes[aId] = [];
  6544.     }
  6545.   },
  6546.  
  6547.   removeDisplay: function CS_removeDisplay(aId)
  6548.   {
  6549.     try {
  6550.       delete this.consoleDisplays[aId];
  6551.       delete this.displayIndexes[aId];
  6552.     }
  6553.     catch (ex) {
  6554.       Cu.reportError("Could not remove console display for id " + aId);
  6555.     }
  6556.   }
  6557. };
  6558.  
  6559. /**
  6560.  * A Console log entry
  6561.  *
  6562.  * @param JSObject aConfig, object literal with ConsolEntry properties
  6563.  * @param integer aId
  6564.  * @returns void
  6565.  */
  6566.  
  6567. function ConsoleEntry(aConfig, id)
  6568. {
  6569.   if (!aConfig.logLevel && aConfig.message) {
  6570.     throw new Error("Missing Arguments when creating a console entry");
  6571.   }
  6572.  
  6573.   this.config = aConfig;
  6574.   this.id = id;
  6575.   for (var prop in aConfig) {
  6576.     if (!(typeof aConfig[prop] == "function")){
  6577.       this[prop] = aConfig[prop];
  6578.     }
  6579.   }
  6580.  
  6581.   if (aConfig.logLevel == "network") {
  6582.     this.transactions = { };
  6583.     if (aConfig.activity) {
  6584.       this.transactions[aConfig.activity.stage] = aConfig.activity;
  6585.     }
  6586.   }
  6587.  
  6588. }
  6589.  
  6590. ConsoleEntry.prototype = {
  6591.  
  6592.   updateTransaction: function CE_updateTransaction(aActivity) {
  6593.     this.transactions[aActivity.stage] = aActivity;
  6594.   }
  6595. };
  6596.  
  6597. //////////////////////////////////////////////////////////////////////////
  6598. // HUDWindowObserver
  6599. //////////////////////////////////////////////////////////////////////////
  6600.  
  6601. HUDWindowObserver = {
  6602.   QueryInterface: XPCOMUtils.generateQI(
  6603.     [Ci.nsIObserver,]
  6604.   ),
  6605.  
  6606.   init: function HWO_init()
  6607.   {
  6608.     Services.obs.addObserver(this, "xpcom-shutdown", false);
  6609.     Services.obs.addObserver(this, "content-document-global-created", false);
  6610.   },
  6611.  
  6612.   observe: function HWO_observe(aSubject, aTopic, aData)
  6613.   {
  6614.     if (aTopic == "content-document-global-created") {
  6615.       HUDService.windowInitializer(aSubject);
  6616.     }
  6617.     else if (aTopic == "xpcom-shutdown") {
  6618.       this.uninit();
  6619.     }
  6620.   },
  6621.  
  6622.   uninit: function HWO_uninit()
  6623.   {
  6624.     Services.obs.removeObserver(this, "content-document-global-created");
  6625.     Services.obs.removeObserver(this, "xpcom-shutdown");
  6626.     this.initialConsoleCreated = false;
  6627.   },
  6628.  
  6629. };
  6630.  
  6631. ///////////////////////////////////////////////////////////////////////////////
  6632. // CommandController
  6633. ///////////////////////////////////////////////////////////////////////////////
  6634.  
  6635. /**
  6636.  * A controller (an instance of nsIController) that makes editing actions
  6637.  * behave appropriately in the context of the Web Console.
  6638.  */
  6639. function CommandController(aWindow) {
  6640.   this.window = aWindow;
  6641. }
  6642.  
  6643. CommandController.prototype = {
  6644.   /**
  6645.    * Returns the HUD output node that currently has the focus, or null if the
  6646.    * currently-focused element isn't inside the output node.
  6647.    *
  6648.    * @returns nsIDOMNode
  6649.    *          The currently-focused output node.
  6650.    */
  6651.   _getFocusedOutputNode: function CommandController_getFocusedOutputNode()
  6652.   {
  6653.     let element = this.window.document.commandDispatcher.focusedElement;
  6654.     if (element && element.classList.contains("hud-output-node")) {
  6655.       return element;
  6656.     }
  6657.     return null;
  6658.   },
  6659.  
  6660.   /**
  6661.    * Copies the currently-selected entries in the Web Console output to the
  6662.    * clipboard.
  6663.    *
  6664.    * @param nsIDOMNode aOutputNode
  6665.    *        The Web Console output node.
  6666.    * @returns void
  6667.    */
  6668.   copy: function CommandController_copy(aOutputNode)
  6669.   {
  6670.     HUDService.copySelectedItems(aOutputNode);
  6671.   },
  6672.  
  6673.   /**
  6674.    * Selects all the text in the HUD output.
  6675.    *
  6676.    * @param nsIDOMNode aOutputNode
  6677.    *        The HUD output node.
  6678.    * @returns void
  6679.    */
  6680.   selectAll: function CommandController_selectAll(aOutputNode)
  6681.   {
  6682.     aOutputNode.selectAll();
  6683.   },
  6684.  
  6685.   supportsCommand: function CommandController_supportsCommand(aCommand)
  6686.   {
  6687.     return this.isCommandEnabled(aCommand);
  6688.   },
  6689.  
  6690.   isCommandEnabled: function CommandController_isCommandEnabled(aCommand)
  6691.   {
  6692.     let outputNode = this._getFocusedOutputNode();
  6693.     if (!outputNode) {
  6694.       return false;
  6695.     }
  6696.  
  6697.     switch (aCommand) {
  6698.       case "cmd_copy":
  6699.         // Only enable "copy" if nodes are selected.
  6700.         return outputNode.selectedCount > 0;
  6701.       case "cmd_selectAll":
  6702.         // "Select All" is always enabled.
  6703.         return true;
  6704.     }
  6705.   },
  6706.  
  6707.   doCommand: function CommandController_doCommand(aCommand)
  6708.   {
  6709.     let outputNode = this._getFocusedOutputNode();
  6710.     switch (aCommand) {
  6711.       case "cmd_copy":
  6712.         this.copy(outputNode);
  6713.         break;
  6714.       case "cmd_selectAll":
  6715.         this.selectAll(outputNode);
  6716.         break;
  6717.     }
  6718.   }
  6719. };
  6720.  
  6721. ///////////////////////////////////////////////////////////////////////////////
  6722. // HUDConsoleObserver
  6723. ///////////////////////////////////////////////////////////////////////////////
  6724.  
  6725. /**
  6726.  * HUDConsoleObserver: Observes nsIConsoleService for global consoleMessages,
  6727.  * if a message originates inside a contentWindow we are tracking,
  6728.  * then route that message to the HUDService for logging.
  6729.  */
  6730.  
  6731. HUDConsoleObserver = {
  6732.   QueryInterface: XPCOMUtils.generateQI(
  6733.     [Ci.nsIObserver]
  6734.   ),
  6735.  
  6736.   init: function HCO_init()
  6737.   {
  6738.     Services.console.registerListener(this);
  6739.     Services.obs.addObserver(this, "xpcom-shutdown", false);
  6740.   },
  6741.  
  6742.   uninit: function HCO_uninit()
  6743.   {
  6744.     Services.console.unregisterListener(this);
  6745.     Services.obs.removeObserver(this, "xpcom-shutdown");
  6746.   },
  6747.  
  6748.   observe: function HCO_observe(aSubject, aTopic, aData)
  6749.   {
  6750.     if (aTopic == "xpcom-shutdown") {
  6751.       this.uninit();
  6752.       return;
  6753.     }
  6754.  
  6755.     if (!(aSubject instanceof Ci.nsIScriptError) ||
  6756.         !(aSubject instanceof Ci.nsIScriptError2) ||
  6757.         !aSubject.outerWindowID) {
  6758.       return;
  6759.     }
  6760.  
  6761.     switch (aSubject.category) {
  6762.       // We ignore chrome-originating errors as we only
  6763.       // care about content.
  6764.       case "XPConnect JavaScript":
  6765.       case "component javascript":
  6766.       case "chrome javascript":
  6767.       case "chrome registration":
  6768.       case "XBL":
  6769.       case "XBL Prototype Handler":
  6770.       case "XBL Content Sink":
  6771.       case "xbl javascript":
  6772.         return;
  6773.  
  6774.       case "CSS Parser":
  6775.       case "CSS Loader":
  6776.         HUDService.reportPageError(CATEGORY_CSS, aSubject);
  6777.         return;
  6778.  
  6779.       default:
  6780.         HUDService.reportPageError(CATEGORY_JS, aSubject);
  6781.         return;
  6782.     }
  6783.   }
  6784. };
  6785.  
  6786. /**
  6787.  * A WebProgressListener that listens for location changes, to update HUDService
  6788.  * state information on page navigation.
  6789.  *
  6790.  * @constructor
  6791.  * @param string aHudId
  6792.  *        The HeadsUpDisplay ID.
  6793.  */
  6794. function ConsoleProgressListener(aHudId)
  6795. {
  6796.   this.hudId = aHudId;
  6797. }
  6798.  
  6799. ConsoleProgressListener.prototype = {
  6800.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  6801.                                          Ci.nsISupportsWeakReference]),
  6802.  
  6803.   onStateChange: function CPL_onStateChange(aProgress, aRequest, aState,
  6804.                                             aStatus)
  6805.   {
  6806.     if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
  6807.       return;
  6808.     }
  6809.  
  6810.     let uri = null;
  6811.     if (aRequest instanceof Ci.imgIRequest) {
  6812.       let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
  6813.       uri = imgIRequest.URI;
  6814.     }
  6815.     else if (aRequest instanceof Ci.nsIChannel) {
  6816.       let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
  6817.       uri = nsIChannel.URI;
  6818.     }
  6819.  
  6820.     if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
  6821.       return;
  6822.     }
  6823.  
  6824.     let outputNode = HUDService.hudReferences[this.hudId].outputNode;
  6825.  
  6826.     let chromeDocument = outputNode.ownerDocument;
  6827.     let msgNode = chromeDocument.createElementNS(HTML_NS, "html:span");
  6828.  
  6829.     // Create the clickable URL part of the message.
  6830.     let linkNode = chromeDocument.createElementNS(HTML_NS, "html:span");
  6831.     linkNode.appendChild(chromeDocument.createTextNode(uri.spec));
  6832.     linkNode.classList.add("hud-clickable");
  6833.     linkNode.classList.add("webconsole-msg-url");
  6834.  
  6835.     linkNode.addEventListener("mousedown", function(aEvent) {
  6836.       this._startX = aEvent.clientX;
  6837.       this._startY = aEvent.clientY;
  6838.     }, false);
  6839.  
  6840.     linkNode.addEventListener("click", function(aEvent) {
  6841.       if (aEvent.detail == 1 && aEvent.button == 0 &&
  6842.           this._startX == aEvent.clientX && this._startY == aEvent.clientY) {
  6843.         let viewSourceUtils = chromeDocument.defaultView.gViewSourceUtils;
  6844.         viewSourceUtils.viewSource(uri.spec, null, chromeDocument);
  6845.       }
  6846.     }, false);
  6847.  
  6848.     msgNode.appendChild(linkNode);
  6849.  
  6850.     let messageNode = ConsoleUtils.createMessageNode(chromeDocument,
  6851.                                                      CATEGORY_NETWORK,
  6852.                                                      SEVERITY_LOG,
  6853.                                                      msgNode,
  6854.                                                      this.hudId,
  6855.                                                      null,
  6856.                                                      null,
  6857.                                                      uri.spec);
  6858.  
  6859.     ConsoleUtils.outputMessageNode(messageNode, this.hudId);
  6860.   },
  6861.  
  6862.   onLocationChange: function() {},
  6863.   onStatusChange: function() {},
  6864.   onProgressChange: function() {},
  6865.   onSecurityChange: function() {},
  6866. };
  6867.  
  6868. ///////////////////////////////////////////////////////////////////////////
  6869. // appName
  6870. ///////////////////////////////////////////////////////////////////////////
  6871.  
  6872. /**
  6873.  * Get the app's name so we can properly dispatch app-specific
  6874.  * methods per API call
  6875.  * @returns Gecko application name
  6876.  */
  6877. function appName()
  6878. {
  6879.   let APP_ID = Services.appinfo.QueryInterface(Ci.nsIXULRuntime).ID;
  6880.  
  6881.   let APP_ID_TABLE = {
  6882.     "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "FIREFOX" ,
  6883.     "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "THUNDERBIRD",
  6884.     "{a23983c0-fd0e-11dc-95ff-0800200c9a66}": "FENNEC" ,
  6885.     "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SEAMONKEY",
  6886.   };
  6887.  
  6888.   let name = APP_ID_TABLE[APP_ID];
  6889.  
  6890.   if (name){
  6891.     return name;
  6892.   }
  6893.   throw new Error("appName: UNSUPPORTED APPLICATION UUID");
  6894. }
  6895.  
  6896. ///////////////////////////////////////////////////////////////////////////
  6897. // HUDService (exported symbol)
  6898. ///////////////////////////////////////////////////////////////////////////
  6899.  
  6900. try {
  6901.   // start the HUDService
  6902.   // This is in a try block because we want to kill everything if
  6903.   // *any* of this fails
  6904.   var HUDService = new HUD_SERVICE();
  6905. }
  6906. catch (ex) {
  6907.   Cu.reportError("HUDService failed initialization.\n" + ex);
  6908.   // TODO: kill anything that may have started up
  6909.   // see bug 568665
  6910. }
  6911.  
  6912. ///////////////////////////////////////////////////////////////////////////
  6913. // GcliTerm
  6914. ///////////////////////////////////////////////////////////////////////////
  6915.  
  6916. /**
  6917.  * Some commands need customization - this is how we get at them.
  6918.  */
  6919. let commandExports = undefined;
  6920.  
  6921. /**
  6922.  * GcliTerm
  6923.  *
  6924.  * Initialize GCLI by creating a set of startup options from the available
  6925.  * properties.
  6926.  *
  6927.  * @param nsIDOMWindow aContentWindow
  6928.  *        The content window that we're providing as the context to commands
  6929.  * @param string aHudId
  6930.  *        The HUD to which we should send console messages.
  6931.  * @param nsIDOMDocument aDocument
  6932.  *        The DOM document from which to create nodes.
  6933.  * @param object aConsole
  6934.  *        Console object to use within the GcliTerm.
  6935.  * @param nsIDOMElement aHintNode
  6936.  *        The node to which we add GCLI's hints.
  6937.  * @constructor
  6938.  */
  6939. function GcliTerm(aContentWindow, aHudId, aDocument, aConsole, aHintNode)
  6940. {
  6941.   this.context = Cu.getWeakReference(aContentWindow);
  6942.   this.hudId = aHudId;
  6943.   this.document = aDocument;
  6944.   this.console = aConsole;
  6945.   this.hintNode = aHintNode;
  6946.  
  6947.   this.createUI();
  6948.   this.createSandbox();
  6949.  
  6950.   this.show = this.show.bind(this);
  6951.   this.hide = this.hide.bind(this);
  6952.  
  6953.   this.opts = {
  6954.     environment: { hudId: this.hudId },
  6955.     chromeDocument: this.document,
  6956.     contentDocument: aContentWindow.document,
  6957.     jsEnvironment: {
  6958.       globalObject: unwrap(aContentWindow),
  6959.       evalFunction: this.evalInSandbox.bind(this)
  6960.     },
  6961.     inputElement: this.inputNode,
  6962.     completeElement: this.completeNode,
  6963.     inputBackgroundElement: this.inputStack,
  6964.     hintElement: this.hintNode,
  6965.     completionPrompt: "",
  6966.     gcliTerm: this
  6967.   };
  6968.  
  6969.   gcli._internal.commandOutputManager.addListener(this.onCommandOutput, this);
  6970.   gcli._internal.createView(this.opts);
  6971.  
  6972.   if (!commandExports) {
  6973.     commandExports = loadCommands();
  6974.   }
  6975. }
  6976.  
  6977. GcliTerm.prototype = {
  6978.   /**
  6979.    * Remove the hint column from the display.
  6980.    */
  6981.   hide: function GcliTerm_hide()
  6982.   {
  6983.     this.hintNode.parentNode.hidden = true;
  6984.   },
  6985.  
  6986.   /**
  6987.    * Undo the effects of calling hide().
  6988.    */
  6989.   show: function GcliTerm_show()
  6990.   {
  6991.     this.hintNode.parentNode.hidden = false;
  6992.   },
  6993.  
  6994.   /**
  6995.    * Destroy the GcliTerm object. Call this method to avoid memory leaks.
  6996.    */
  6997.   destroy: function Gcli_destroy()
  6998.   {
  6999.     gcli._internal.removeView(this.opts);
  7000.     gcli._internal.commandOutputManager.removeListener(this.onCommandOutput, this);
  7001.  
  7002.     delete this.opts.chromeDocument;
  7003.     delete this.opts.inputElement;
  7004.     delete this.opts.completeElement;
  7005.     delete this.opts.inputBackgroundElement;
  7006.     delete this.opts.hintElement;
  7007.     delete this.opts.contentDocument;
  7008.     delete this.opts.jsEnvironment;
  7009.     delete this.opts.gcliTerm;
  7010.  
  7011.     delete this.context;
  7012.     delete this.document;
  7013.     delete this.console;
  7014.     delete this.hintNode;
  7015.  
  7016.     delete this.sandbox;
  7017.     delete this.element
  7018.     delete this.inputStack
  7019.     delete this.completeNode
  7020.     delete this.inputNode
  7021.   },
  7022.  
  7023.   /**
  7024.    * Re-attaches a console when the contentWindow is recreated.
  7025.    *
  7026.    * @param nsIDOMWindow aContentWindow
  7027.    *        The content window that we're providing as the context to commands
  7028.    * @param object aConsole
  7029.    *        Console object to use within the GcliTerm.
  7030.    */
  7031.   reattachConsole: function Gcli_reattachConsole(aContentWindow, aConsole)
  7032.   {
  7033.     this.context = Cu.getWeakReference(aContentWindow);
  7034.     this.console = aConsole;
  7035.     this.createSandbox();
  7036.   },
  7037.  
  7038.   /**
  7039.    * Generates and attaches the GCLI Terminal part of the Web Console, which
  7040.    * essentially consists of the interactive JavaScript input facility.
  7041.    */
  7042.   createUI: function Gcli_createUI()
  7043.   {
  7044.     this.element = this.document.createElement("vbox");
  7045.     this.element.setAttribute("class", "gcliterm-input-container");
  7046.     this.element.setAttribute("flex", "0");
  7047.  
  7048.     this.inputStack = this.document.createElement("stack");
  7049.     this.inputStack.setAttribute("class", "gcliterm-stack-node");
  7050.     this.element.appendChild(this.inputStack);
  7051.  
  7052.     this.completeNode = this.document.createElement("div");
  7053.     this.completeNode.setAttribute("class", "gcliterm-complete-node");
  7054.     this.completeNode.setAttribute("aria-live", "polite");
  7055.     this.inputStack.appendChild(this.completeNode);
  7056.  
  7057.     this.inputNode = this.document.createElement("textbox");
  7058.     this.inputNode.setAttribute("class", "gcliterm-input-node");
  7059.     this.inputNode.setAttribute("rows", "1");
  7060.     this.inputStack.appendChild(this.inputNode);
  7061.   },
  7062.  
  7063.   /**
  7064.    * Called by GCLI/canon when command line output changes.
  7065.    */
  7066.   onCommandOutput: function Gcli_onCommandOutput(aEvent)
  7067.   {
  7068.     // When we can update the history of the console, then we should stop
  7069.     // filtering incomplete reports.
  7070.     if (!aEvent.output.completed) {
  7071.       return;
  7072.     }
  7073.  
  7074.     this.writeOutput(aEvent.output.typed, { category: CATEGORY_INPUT });
  7075.  
  7076.     if (aEvent.output.output == null) {
  7077.       return;
  7078.     }
  7079.  
  7080.     let output = aEvent.output.output;
  7081.     if (aEvent.output.command.returnType == "html" && typeof output == "string") {
  7082.       let frag = this.document.createRange().createContextualFragment(
  7083.           '<div xmlns="' + HTML_NS + '" xmlns:xul="' + XUL_NS + '">' +
  7084.           output + '</div>');
  7085.  
  7086.       output = this.document.createElementNS(HTML_NS, "div");
  7087.       output.appendChild(frag);
  7088.     }
  7089.     this.writeOutput(output);
  7090.   },
  7091.  
  7092.   /**
  7093.    * Setup the eval sandbox, should be called whenever we are attached.
  7094.    */
  7095.   createSandbox: function Gcli_createSandbox()
  7096.   {
  7097.     let win = this.context.get().QueryInterface(Ci.nsIDOMWindow);
  7098.  
  7099.     // create a JS Sandbox out of this.context
  7100.     this.sandbox = new Cu.Sandbox(win, {
  7101.       sandboxPrototype: win,
  7102.       wantXrays: false
  7103.     });
  7104.     this.sandbox.console = this.console;
  7105.   },
  7106.  
  7107.   /**
  7108.    * Evaluates a string in the sandbox.
  7109.    *
  7110.    * @param string aString
  7111.    *        String to evaluate in the sandbox
  7112.    * @return The result of the evaluation
  7113.    */
  7114.   evalInSandbox: function Gcli_evalInSandbox(aString)
  7115.   {
  7116.     return Cu.evalInSandbox(aString, this.sandbox, "1.8", "Web Console", 1);
  7117.   },
  7118.  
  7119.   /**
  7120.    * Writes a message to the HUD that originates from the interactive
  7121.    * JavaScript console.
  7122.    *
  7123.    * @param string aOutputMessage
  7124.    *        The message to display.
  7125.    * @param number aCategory
  7126.    *        One of the CATEGORY_ constants.
  7127.    * @param number aSeverity
  7128.    *        One of the SEVERITY_ constants.
  7129.    */
  7130.   writeOutput: function Gcli_writeOutput(aOutputMessage, aOptions)
  7131.   {
  7132.     aOptions = aOptions || {};
  7133.  
  7134.     let node = ConsoleUtils.createMessageNode(
  7135.                     this.document,
  7136.                     aOptions.category || CATEGORY_OUTPUT,
  7137.                     aOptions.severity || SEVERITY_LOG,
  7138.                     aOutputMessage,
  7139.                     this.hudId,
  7140.                     aOptions.sourceUrl || undefined,
  7141.                     aOptions.sourceLine || undefined,
  7142.                     aOptions.clipboardText || undefined);
  7143.  
  7144.     ConsoleUtils.outputMessageNode(node, this.hudId);
  7145.   },
  7146.  
  7147.   clearOutput: JSTerm.prototype.clearOutput,
  7148. };
  7149.  
  7150.